def format_exc_info(exc_info): _, _, fullnames, message = parse_exc_info(exc_info) fullname = fullnames[0] return { "error.class": fullname, "error.message": message, "error.expected": is_expected_error(exc_info), }
def error_matches_rules( rules_prefix, exc_info, status_code=None, ): # Delay imports to prevent lockups from newrelic.api.application import application_instance from newrelic.core.trace_cache import trace_cache trace = trace_cache().current_trace() settings = trace and trace.settings if not settings: # Retrieve application settings application = application_instance() settings = application and application.settings # Default to global settings settings = settings or global_settings() if not settings: return False # Retrieve settings based on prefix classes_rules = getattr(settings.error_collector, "%s_classes" % rules_prefix, set()) status_codes_rules = getattr(settings.error_collector, "%s_status_codes" % rules_prefix, set()) module, name, fullnames, message = parse_exc_info(exc_info) fullname = fullnames[0] # Check class names for fullname in fullnames: if fullname in classes_rules: return True # Check status_code # For callables, call on exc_info to retrieve status_code. # It's possible to return None, in which case no code is evaluated. if callable(status_code): status_code = status_code(*exc_info) # Match status_code if it exists if status_code is not None: try: # Coerce into integer status_code = int(status_code) except: _logger.error("Failed to coerce status code into integer. " "status_code: %s" % str(status_code)) else: if status_code in status_codes_rules: return True return False
def format_exc_info(exc_info): _, _, fullnames, message = parse_exc_info(exc_info) fullname = fullnames[0] formatted = { "error.class": fullname, "error.message": message, } expected = is_expected_error(exc_info) if expected is not None: formatted["error.expected"] = expected return formatted
def record_exception(self, exc_info=None, params={}, ignore_errors=[]): # Deprecation Warning warnings.warn( ('The record_exception function is deprecated. Please use the ' 'new api named notice_error instead.'), DeprecationWarning) transaction = self.transaction # Pull from sys.exc_info if no exception is passed if not exc_info or None in exc_info: exc_info = sys.exc_info() # If no exception to report, exit if not exc_info or None in exc_info: return exc, value, tb = exc_info # Check ignore_errors callables # We check these here separatly from the notice_error implementation # to preserve previous functionality in precedence should_ignore = None if hasattr(transaction, '_ignore_errors'): should_ignore = transaction._ignore_errors(exc, value, tb) if should_ignore: return if callable(ignore_errors): should_ignore = ignore_errors(exc, value, tb) if should_ignore: return # Check ignore_errors iterables if should_ignore is None and not callable(ignore_errors): _, _, fullnames, _ = parse_exc_info(exc_info) for fullname in fullnames: if fullname in ignore_errors: return self.notice_error(error=exc_info, attributes=params, ignore=should_ignore)
def ignore_graphql_duplicate_exception(exc, val, tb): from graphql.error import GraphQLError if isinstance(val, GraphQLError): transaction = current_transaction() # Check that we have not recorded this exception # previously for this transaction due to multiple # error traces triggering. This happens if an exception # is reraised by GraphQL as a new GraphQLError type # after the original exception has already been recorded. if transaction and hasattr(val, "original_error"): while hasattr(val, "original_error"): # Unpack lowest level original error val = val.original_error _, _, fullnames, message = parse_exc_info((None, val, None)) fullname = fullnames[0] for error in transaction._errors: if error.type == fullname and error.message == message: return True return None # Follow original exception matching rules
def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_code=None): # Bail out if the transaction is not active or # collection of errors not enabled. transaction = self.transaction settings = transaction and transaction.settings if not settings: return if not settings.error_collector.enabled: return # At least one destination for error events must be enabled if not (settings.collect_traces or settings.collect_span_events or settings.collect_errors or settings.collect_error_events): return # If no exception details provided, use current exception. # Pull from sys.exc_info if no exception is passed if not exc_info or None in exc_info: exc_info = sys.exc_info() # If no exception to report, exit if not exc_info or None in exc_info: return exc, value, tb = exc_info if getattr(value, "_nr_ignored", None): return module, name, fullnames, message = parse_exc_info((exc, value, tb)) fullname = fullnames[0] # Check to see if we need to strip the message before recording it. if settings.strip_exception_messages.enabled and fullname not in settings.strip_exception_messages.allowlist: message = STRIP_EXCEPTION_MESSAGE # Where expected or ignore are a callable they should return a # tri-state variable with the following behavior. # # True - Ignore the error. # False- Record the error. # None - Use the default rules. # Precedence: # 1. function parameter override as bool # 2. function parameter callable # 3. callable on transaction # 4. function parameter iterable of class names # 5. default rule matching from settings should_ignore = None is_expected = None # Check against ignore rules # Boolean parameter (True/False only, not None) if isinstance(ignore, bool): should_ignore = ignore if should_ignore: value._nr_ignored = True return # Callable parameter if should_ignore is None and callable(ignore): should_ignore = ignore(exc, value, tb) if should_ignore: value._nr_ignored = True return # Callable on transaction if should_ignore is None and hasattr(transaction, "_ignore_errors"): should_ignore = transaction._ignore_errors(exc, value, tb) if should_ignore: value._nr_ignored = True return # List of class names if should_ignore is None and ignore is not None and not callable( ignore): # Do not set should_ignore to False # This should cascade into default settings rule matching for name in fullnames: if name in ignore: value._nr_ignored = True return # Default rule matching if should_ignore is None: should_ignore = should_ignore_error(exc_info, status_code=status_code, settings=settings) if should_ignore: value._nr_ignored = True return # Check against expected rules # Boolean parameter (True/False only, not None) if isinstance(expected, bool): is_expected = expected # Callable parameter if is_expected is None and callable(expected): is_expected = expected(exc, value, tb) # List of class names if is_expected is None and expected is not None and not callable( expected): # Do not set is_expected to False # This should cascade into default settings rule matching for name in fullnames: if name in expected: is_expected = True # Default rule matching if is_expected is None: is_expected = is_expected_error(exc_info, status_code=status_code, settings=settings) # Record a supportability metric if error attributes are being # overiden. if "error.class" in self.agent_attributes: transaction._record_supportability( "Supportability/SpanEvent/Errors/Dropped") # Add error details as agent attributes to span event. self._add_agent_attribute("error.class", fullname) self._add_agent_attribute("error.message", message) self._add_agent_attribute("error.expected", is_expected) return fullname, message, tb, is_expected
def error_matches_rules( rules_prefix, exc_info, status_code=None, settings=None, ): """ Attempt to match exception to rules based on prefix. rules_prefix is one of [ignore, expected] exc_info is an exception tuple of (exc, val, tb) status_code is an optional value or callable taking in exc_info that returns an int-like object origin is either the current application or trace. """ # Delay imports to prevent lockups from newrelic.core.trace_cache import trace_cache if not settings: # Pull from current transaction if no settings provided tc = trace_cache() transaction = tc.current_transaction() settings = transaction and transaction.settings if not settings: # Pull from active trace if no settings on transaction trace = tc.current_trace() settings = trace and trace.settings if not settings: # Unable to find rules to match with _logger.debug( "Failed to retrieve exception rules: No settings supplied, or found on transaction or trace." ) return None # Retrieve settings based on prefix classes_rules = getattr(settings.error_collector, "%s_classes" % rules_prefix, set()) status_codes_rules = getattr(settings.error_collector, "%s_status_codes" % rules_prefix, set()) _, _, fullnames, _ = parse_exc_info(exc_info) fullname = fullnames[0] # Check class names for fullname in fullnames: if fullname in classes_rules: return True # Check status_code # For callables, call on exc_info to retrieve status_code. # It's possible to return None, in which case no code is evaluated. if callable(status_code): status_code = status_code(*exc_info) # Match status_code if it exists if status_code is not None: try: # Coerce into integer status_code = int(status_code) except: _logger.error( "Failed to coerce status code into integer. status_code: %s" % str(status_code)) else: if status_code in status_codes_rules: return True return False
def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_code=None): # Bail out if the transaction is not active or # collection of errors not enabled. transaction = self.transaction settings = transaction and transaction.settings if not settings: return if not settings.error_collector.enabled: return # At least one destination for error events must be enabled if not (settings.collect_traces or settings.collect_span_events or settings.collect_errors or settings.collect_error_events): return # If no exception details provided, use current exception. if exc_info and None not in exc_info: exc, value, tb = exc_info else: exc, value, tb = sys.exc_info() # Has to be an error to be logged. if exc is None or value is None or tb is None: return module, name, fullnames, message = parse_exc_info((exc, value, tb)) fullname = fullnames[0] # Check to see if we need to strip the message before recording it. if (settings.strip_exception_messages.enabled and fullname not in settings.strip_exception_messages.whitelist): message = STRIP_EXCEPTION_MESSAGE # Where expected or ignore are a callable they should return a # tri-state variable with the following behavior. # # True - Ignore the error. # False- Record the error. # None - Use the default rules. # Precedence: # 1. function parameter override as bool # 2. function parameter callable # 3. callable on transaction # 4. default rule matching from settings should_ignore = None is_expected = None # Check against ignore rules # Boolean parameter (True/False only, not None) if isinstance(ignore, bool): should_ignore = ignore if should_ignore: return # Callable parameter if should_ignore is None and callable(ignore): should_ignore = ignore(exc, value, tb) if should_ignore: return # Callable on transaction if should_ignore is None and hasattr(transaction, '_ignore_errors'): should_ignore = transaction._ignore_errors(exc, value, tb) if should_ignore: return # Default rule matching if should_ignore is None: should_ignore = should_ignore_error((exc, value, tb), status_code=status_code) if should_ignore: return # Check against expected rules # Boolean parameter (True/False only, not None) if isinstance(expected, bool): is_expected = expected # Callable parameter if is_expected is None and callable(expected): is_expected = expected(exc, value, tb) # Callable on transaction if is_expected is None and hasattr(transaction, '_expected_errors'): is_expected = transaction._expected_errors(exc, value, tb) # Default rule matching if is_expected is None: is_expected = is_expected_error((exc, value, tb), status_code=status_code) # Record a supportability metric if error attributes are being # overiden. if 'error.class' in self.agent_attributes: transaction._record_supportability('Supportability/' 'SpanEvent/Errors/Dropped') # Add error details as agent attributes to span event. self._add_agent_attribute("error.class", fullname) self._add_agent_attribute("error.message", message) self._add_agent_attribute("error.expected", is_expected) return fullname, message, tb, is_expected