Exemplo n.º 1
0
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),
    }
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
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
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
    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