Class: NewRelic::Agent::ErrorCollector
- Inherits:
-
Object
- Object
- NewRelic::Agent::ErrorCollector
- Defined in:
- lib/new_relic/agent/error_collector.rb
Overview
This class collects errors from the parent application, storing them until they are harvested and transmitted to the server
Constant Summary collapse
- MAX_ERROR_QUEUE_LENGTH =
Maximum possible length of the queue - defaults to 20, may be made configurable in the future. This is a tradeoff between memory and data retention
20
- EXCEPTION_TAG_IVAR =
:'@__nr_seen_exception'
Instance Attribute Summary collapse
-
#error_event_aggregator ⇒ Object
readonly
Returns the value of attribute error_event_aggregator.
-
#error_trace_aggregator ⇒ Object
readonly
Returns the value of attribute error_trace_aggregator.
Class Method Summary collapse
- .ignore_error_filter ⇒ Object
-
.ignore_error_filter=(block) ⇒ Object
We store the passed block in both an ivar on the class, and implicitly within the body of the ignore_filter_proc method intentionally here.
Instance Method Summary collapse
- #aggregated_metric_names(txn) ⇒ Object
- #blamed_metric_name(txn, options) ⇒ Object
- #create_noticed_error(exception, options) ⇒ Object
- #disabled? ⇒ Boolean
- #drop_buffered_data ⇒ Object
- #enabled? ⇒ Boolean
-
#error_affects_apdex?(error, options) ⇒ Boolean
Neither ignored nor expected errors impact apdex.
-
#error_is_ignored?(error, status_code = nil) ⇒ Boolean
an error is ignored if it is nil or if it is filtered.
-
#exception_is_java_object?(exception) ⇒ Boolean
Calling instance_variable_set on a wrapped Java object in JRuby will generate a warning unless that object’s class has already been marked as persistent, so we skip tagging of exception objects that are actually wrapped Java objects on JRuby.
- #exception_tagged_with?(ivar, exception) ⇒ Boolean
- #expect(errors) ⇒ Object
- #expected?(ex, status_code = nil) ⇒ Boolean
-
#extract_stack_trace(exception) ⇒ Object
extracts a stack trace from the exception for debugging purposes.
- #ignore(errors) ⇒ Object
- #ignore?(ex, status_code = nil) ⇒ Boolean
-
#ignored_by_filter_proc?(error) ⇒ Boolean
Checks the provided error against the error filter, if there is an error filter.
-
#increment_error_count!(state, exception, options = {}) ⇒ Object
Increments a statistic that tracks total error rate.
- #increment_expected_error_count!(state, exception) ⇒ Object
-
#initialize(events) ⇒ ErrorCollector
constructor
Returns a new error collector.
- #load_error_filters ⇒ Object
-
#notice_agent_error(exception) ⇒ Object
*Use sparingly for difficult to track bugs.*.
-
#notice_error(exception, options = {}, span_id = nil) ⇒ Object
See NewRelic::Agent.notice_error for options and commentary.
- #notice_segment_error(segment, exception, options = {}) ⇒ Object
- #reset_error_filters ⇒ Object
-
#sense_method(object, method) ⇒ Object
calls a method on an object, if it responds to it - used for detection and soft fail-safe.
- #skip_notice_error?(exception, status_code = nil) ⇒ Boolean
- #tag_exception(exception) ⇒ Object
- #tag_exception_using(ivar, exception) ⇒ Object
- #truncate_trace(trace, keep_frames = nil) ⇒ Object
Constructor Details
#initialize(events) ⇒ ErrorCollector
Returns a new error collector
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/new_relic/agent/error_collector.rb', line 22 def initialize(events) @error_trace_aggregator = ErrorTraceAggregator.new(MAX_ERROR_QUEUE_LENGTH) @error_event_aggregator = ErrorEventAggregator.new(events) @error_filter = NewRelic::Agent::ErrorFilter.new %w[ ignore_classes ignore_messages ignore_status_codes expected_classes expected_messages expected_status_codes ].each do |w| Agent.config.register_callback(:"error_collector.#{w}") do |value| @error_filter.load_from_config(w, value) end end end |
Instance Attribute Details
#error_event_aggregator ⇒ Object (readonly)
Returns the value of attribute error_event_aggregator.
19 20 21 |
# File 'lib/new_relic/agent/error_collector.rb', line 19 def error_event_aggregator @error_event_aggregator end |
#error_trace_aggregator ⇒ Object (readonly)
Returns the value of attribute error_trace_aggregator.
19 20 21 |
# File 'lib/new_relic/agent/error_collector.rb', line 19 def error_trace_aggregator @error_trace_aggregator end |
Class Method Details
.ignore_error_filter ⇒ Object
71 72 73 |
# File 'lib/new_relic/agent/error_collector.rb', line 71 def self.ignore_error_filter defined?(@ignore_filter) ? @ignore_filter : nil end |
.ignore_error_filter=(block) ⇒ Object
We store the passed block in both an ivar on the class, and implicitly within the body of the ignore_filter_proc method intentionally here. The define_method trick is needed to get around the fact that users may call ‘return’ from within their filter blocks, which would otherwise result in a LocalJumpError.
The raw block is also stored in an instance variable so that we can return it later in its original form.
This is all done at the class level in order to avoid the case where the user sets up an ignore filter on one instance of the ErrorCollector, and then that instance subsequently gets discarded during agent startup. (For example, if the agent is initially disabled, and then gets enabled via a call to manual_start later on.)
61 62 63 64 65 66 67 68 69 |
# File 'lib/new_relic/agent/error_collector.rb', line 61 def self.ignore_error_filter=(block) @ignore_filter = block if block define_method(:ignore_filter_proc, &block) elsif method_defined?(:ignore_filter_proc) remove_method(:ignore_filter_proc) end @ignore_filter end |
Instance Method Details
#aggregated_metric_names(txn) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/new_relic/agent/error_collector.rb', line 181 def aggregated_metric_names(txn) metric_names = ['Errors/all'] return metric_names unless txn if txn.recording_web_transaction? metric_names << 'Errors/allWeb' else metric_names << 'Errors/allOther' end metric_names end |
#blamed_metric_name(txn, options) ⇒ Object
173 174 175 176 177 178 179 |
# File 'lib/new_relic/agent/error_collector.rb', line 173 def blamed_metric_name(txn, ) if [:metric] && [:metric] != ::NewRelic::Agent::UNKNOWN_METRIC "Errors/#{[:metric]}" else "Errors/#{txn.best_name}" if txn end end |
#create_noticed_error(exception, options) ⇒ Object
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/new_relic/agent/error_collector.rb', line 290 def create_noticed_error(exception, ) error_metric = .delete(:metric) || NewRelic::EMPTY_STR noticed_error = NewRelic::NoticedError.new(error_metric, exception) noticed_error.request_uri = .delete(:uri) || NewRelic::EMPTY_STR noticed_error.request_port = .delete(:port) noticed_error.attributes = .delete(:attributes) noticed_error.file_name = sense_method(exception, :file_name) noticed_error.line_number = sense_method(exception, :line_number) noticed_error.stack_trace = truncate_trace(extract_stack_trace(exception)) noticed_error.expected = !!.delete(:expected) || expected?(exception) # rubocop:disable Style/DoubleNegation noticed_error.attributes_from_notice_error = .delete(:custom_params) || {} # Any options that are passed to notice_error which aren't known keys # get treated as custom attributes, so merge them into that hash. noticed_error.attributes_from_notice_error.merge!() update_error_group_name(noticed_error, exception, ) noticed_error end |
#disabled? ⇒ Boolean
42 43 44 |
# File 'lib/new_relic/agent/error_collector.rb', line 42 def disabled? !enabled? end |
#drop_buffered_data ⇒ Object
328 329 330 331 332 |
# File 'lib/new_relic/agent/error_collector.rb', line 328 def drop_buffered_data @error_trace_aggregator.reset! @error_event_aggregator.reset! nil end |
#enabled? ⇒ Boolean
38 39 40 |
# File 'lib/new_relic/agent/error_collector.rb', line 38 def enabled? error_trace_aggregator.enabled? || error_event_aggregator.enabled? end |
#error_affects_apdex?(error, options) ⇒ Boolean
Neither ignored nor expected errors impact apdex.
Ignored errors are checked via ‘#error_is_ignored?` Expected errors are checked in 2 separate ways:
1. The presence of an `expected: true` attribute key/value pair in the
options hash, which will be set if that key/value pair was used in
the `notice_error` public API.
2. By calling `#expected?` which in turn calls `ErrorFilter#expected?`
which checks for 3 things:
- A match for user-defined HTTP status codes to expect
- A match for user-defined error classes to expect
- A match for user-defined error messages to expect
125 126 127 128 129 130 131 132 133 134 |
# File 'lib/new_relic/agent/error_collector.rb', line 125 def error_affects_apdex?(error, ) return false if error_is_ignored?(error) return false if [:expected] !expected?(error, ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code) rescue => e NewRelic::Agent.logger.error("Could not determine if error '#{error}' should impact Apdex - " \ "#{e.class}: #{e.}. Defaulting to 'true' (it should impact Apdex).") true end |
#error_is_ignored?(error, status_code = nil) ⇒ Boolean
an error is ignored if it is nil or if it is filtered
106 107 108 109 110 111 |
# File 'lib/new_relic/agent/error_collector.rb', line 106 def error_is_ignored?(error, status_code = nil) error && (@error_filter.ignore?(error, status_code) || ignored_by_filter_proc?(error)) rescue => e NewRelic::Agent.logger.error("Error '#{error}' will NOT be ignored. Exception '#{e}' while determining whether to ignore or not.", e) false end |
#exception_is_java_object?(exception) ⇒ Boolean
Calling instance_variable_set on a wrapped Java object in JRuby will generate a warning unless that object’s class has already been marked as persistent, so we skip tagging of exception objects that are actually wrapped Java objects on JRuby.
143 144 145 |
# File 'lib/new_relic/agent/error_collector.rb', line 143 def exception_is_java_object?(exception) NewRelic::LanguageSupport.jruby? && exception.respond_to?(:java_class) end |
#exception_tagged_with?(ivar, exception) ⇒ Boolean
147 148 149 150 151 |
# File 'lib/new_relic/agent/error_collector.rb', line 147 def exception_tagged_with?(ivar, exception) return false if exception_is_java_object?(exception) exception.instance_variable_defined?(ivar) end |
#expect(errors) ⇒ Object
83 84 85 |
# File 'lib/new_relic/agent/error_collector.rb', line 83 def expect(errors) @error_filter.expect(errors) end |
#expected?(ex, status_code = nil) ⇒ Boolean
87 88 89 |
# File 'lib/new_relic/agent/error_collector.rb', line 87 def expected?(ex, status_code = nil) @error_filter.expected?(ex, status_code) end |
#extract_stack_trace(exception) ⇒ Object
extracts a stack trace from the exception for debugging purposes
230 231 232 233 234 235 236 237 |
# File 'lib/new_relic/agent/error_collector.rb', line 230 def extract_stack_trace(exception) actual_exception = if defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR < 5 sense_method(exception, :original_exception) || exception else exception end sense_method(actual_exception, :backtrace) || '<no stack trace>' end |
#ignore(errors) ⇒ Object
75 76 77 |
# File 'lib/new_relic/agent/error_collector.rb', line 75 def ignore(errors) @error_filter.ignore(errors) end |
#ignore?(ex, status_code = nil) ⇒ Boolean
79 80 81 |
# File 'lib/new_relic/agent/error_collector.rb', line 79 def ignore?(ex, status_code = nil) @error_filter.ignore?(ex, status_code) end |
#ignored_by_filter_proc?(error) ⇒ Boolean
Checks the provided error against the error filter, if there is an error filter
101 102 103 |
# File 'lib/new_relic/agent/error_collector.rb', line 101 def ignored_by_filter_proc?(error) respond_to?(:ignore_filter_proc) && !ignore_filter_proc(error) end |
#increment_error_count!(state, exception, options = {}) ⇒ Object
Increments a statistic that tracks total error rate
195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/new_relic/agent/error_collector.rb', line 195 def increment_error_count!(state, exception, = {}) txn = state.current_transaction metric_names = aggregated_metric_names(txn) blamed_metric = blamed_metric_name(txn, ) metric_names << blamed_metric if blamed_metric stats_engine = NewRelic::Agent.agent.stats_engine stats_engine.record_unscoped_metrics(state, metric_names) do |stats| stats.increment_count end end |
#increment_expected_error_count!(state, exception) ⇒ Object
208 209 210 211 212 213 |
# File 'lib/new_relic/agent/error_collector.rb', line 208 def increment_expected_error_count!(state, exception) stats_engine = NewRelic::Agent.agent.stats_engine stats_engine.record_unscoped_metrics(state, ['ErrorsExpected/all']) do |stats| stats.increment_count end end |
#load_error_filters ⇒ Object
91 92 93 |
# File 'lib/new_relic/agent/error_collector.rb', line 91 def load_error_filters @error_filter.load_all end |
#notice_agent_error(exception) ⇒ Object
*Use sparingly for difficult to track bugs.*
Track internal agent errors for communication back to New Relic. To use, make a specific subclass of NewRelic::Agent::InternalAgentError, then pass an instance of it to this method when your problem occurs.
Limits are treated differently for these errors. We only gather one per class per harvest, disregarding (and not impacting) the app error queue limit.
324 325 326 |
# File 'lib/new_relic/agent/error_collector.rb', line 324 def notice_agent_error(exception) @error_trace_aggregator.notice_agent_error(exception) end |
#notice_error(exception, options = {}, span_id = nil) ⇒ Object
See NewRelic::Agent.notice_error for options and commentary
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/new_relic/agent/error_collector.rb', line 253 def notice_error(exception, = {}, span_id = nil) status_code = process_http_status_code(exception, ) return if skip_notice_error?(exception, status_code) tag_exception(exception) state = ::NewRelic::Agent::Tracer.state if [:expected] increment_expected_error_count!(state, exception) else increment_error_count!(state, exception, ) end noticed_error = create_noticed_error(exception, ) error_trace_aggregator.add_to_error_queue(noticed_error) span_id ||= state.current_transaction&.current_segment&.guid error_event_aggregator.record(noticed_error, state.current_transaction&.payload, span_id) exception rescue => e ::NewRelic::Agent.logger.warn("Failure when capturing error '#{exception}':", e) nil end |
#notice_segment_error(segment, exception, options = {}) ⇒ Object
239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/new_relic/agent/error_collector.rb', line 239 def notice_segment_error(segment, exception, = {}) status_code = process_http_status_code(exception, ) return if skip_notice_error?(exception, status_code) .merge!(segment.llm_event.error_attributes(exception)) if segment.llm_event segment.set_noticed_error(create_noticed_error(exception, )) exception rescue => e ::NewRelic::Agent.logger.warn("Failure when capturing segment error '#{exception}':", e) nil end |
#reset_error_filters ⇒ Object
95 96 97 |
# File 'lib/new_relic/agent/error_collector.rb', line 95 def reset_error_filters @error_filter.reset end |
#sense_method(object, method) ⇒ Object
calls a method on an object, if it responds to it - used for detection and soft fail-safe. Returns nil if the method does not exist
225 226 227 |
# File 'lib/new_relic/agent/error_collector.rb', line 225 def sense_method(object, method) object.__send__(method) if object.respond_to?(method) end |
#skip_notice_error?(exception, status_code = nil) ⇒ Boolean
215 216 217 218 219 220 |
# File 'lib/new_relic/agent/error_collector.rb', line 215 def skip_notice_error?(exception, status_code = nil) disabled? || exception.nil? || exception_tagged_with?(EXCEPTION_TAG_IVAR, exception) || error_is_ignored?(exception, status_code) end |
#tag_exception(exception) ⇒ Object
163 164 165 166 167 168 169 170 171 |
# File 'lib/new_relic/agent/error_collector.rb', line 163 def tag_exception(exception) return if exception_is_java_object?(exception) || exception.frozen? begin exception.instance_variable_set(EXCEPTION_TAG_IVAR, true) rescue => e NewRelic::Agent.logger.warn("Failed to tag exception: #{exception}: ", e) end end |
#tag_exception_using(ivar, exception) ⇒ Object
153 154 155 156 157 158 159 160 161 |
# File 'lib/new_relic/agent/error_collector.rb', line 153 def tag_exception_using(ivar, exception) return if exception_is_java_object?(exception) || exception.frozen? begin exception.instance_variable_set(ivar, true) rescue => e NewRelic::Agent.logger.warn("Failed to tag exception: #{exception}: ", e) end end |
#truncate_trace(trace, keep_frames = nil) ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/new_relic/agent/error_collector.rb', line 276 def truncate_trace(trace, keep_frames = nil) keep_frames ||= Agent.config[:'error_collector.max_backtrace_frames'] return trace if !keep_frames || trace.length < keep_frames || trace.length == 0 # If keep_frames is odd, we will split things up favoring the top of the trace keep_top = (keep_frames / 2.0).ceil keep_bottom = (keep_frames / 2.0).floor truncate_frames = trace.length - keep_frames truncated_trace = trace[0...keep_top].concat(["<truncated #{truncate_frames.to_s} additional frames>"]).concat(trace[-keep_bottom..-1]) truncated_trace end |