Esempio n. 1
0
    def __init__(self, value):
        if isinstance(value, Dsn):
            self.__dict__ = dict(value.__dict__)
            return
        parts = urlparse.urlsplit(text_type(value))
        if parts.scheme not in (u"http", u"https"):
            raise BadDsn("Unsupported scheme %r" % parts.scheme)
        self.scheme = parts.scheme
        self.host = parts.hostname
        self.port = parts.port
        if self.port is None:
            self.port = self.scheme == "https" and 443 or 80
        self.public_key = parts.username
        if not self.public_key:
            raise BadDsn("Missing public key")
        self.secret_key = parts.password

        path = parts.path.rsplit("/", 1)

        try:
            self.project_id = text_type(int(path.pop()))
        except (ValueError, TypeError):
            raise BadDsn("Invalid project in DSN (%r)" %
                         (parts.path or "")[1:])

        self.path = "/".join(path) + "/"
Esempio n. 2
0
def fetch_git_sha(path, head=None):
    """ >>> fetch_git_sha(os.path.dirname(__file__))
        Taken from https://git.io/JITmC
    """
    if not head:
        head_path = os.path.join(path, '.git', 'HEAD')
        if not os.path.exists(head_path):
            raise InvalidGitRepository(
                'Cannot identify HEAD for git repository at %s' % (path, ))

        with open(head_path, 'r') as fp:
            head = text_type(fp.read()).strip()

        if head.startswith('ref: '):
            head = head[5:]
            revision_file = os.path.join(path, '.git', *head.split('/'))
        else:
            return head
    else:
        revision_file = os.path.join(path, '.git', 'refs', 'heads', head)

    if not os.path.exists(revision_file):
        if not os.path.exists(os.path.join(path, '.git')):
            raise InvalidGitRepository(
                '%s does not seem to be the root of a git repository' %
                (path, ))

        # Check for our .git/packed-refs' file since a `git gc` may have run
        # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
        packed_file = os.path.join(path, '.git', 'packed-refs')
        if os.path.exists(packed_file):
            with open(packed_file) as fh:
                for line in fh:
                    line = line.rstrip()
                    if line and line[:1] not in ('#', '^'):
                        try:
                            revision, ref = line.split(' ', 1)
                        except ValueError:
                            continue
                        if ref == head:
                            return text_type(revision)

        raise InvalidGitRepository(
            'Unable to find ref to head "%s" in repository' % (head, ))

    with open(revision_file) as fh:
        return text_type(fh.read()).strip()
Esempio n. 3
0
def test_transaction_events(capture_events, init_celery, celery_invocation,
                            task_fails):
    celery = init_celery(traces_sample_rate=1.0)

    @celery.task(name="dummy_task")
    def dummy_task(x, y):
        return x / y

    # XXX: For some reason the first call does not get instrumented properly.
    celery_invocation(dummy_task, 1, 1)

    events = capture_events()

    with start_transaction(name="submission") as transaction:
        celery_invocation(dummy_task, 1, 0 if task_fails else 1)

    if task_fails:
        error_event = events.pop(0)
        assert error_event["contexts"]["trace"][
            "trace_id"] == transaction.trace_id
        assert error_event["exception"]["values"][0][
            "type"] == "ZeroDivisionError"

    execution_event, submission_event = events

    assert execution_event["transaction"] == "dummy_task"
    assert submission_event["transaction"] == "submission"

    assert execution_event["type"] == submission_event["type"] == "transaction"
    assert execution_event["contexts"]["trace"][
        "trace_id"] == transaction.trace_id
    assert submission_event["contexts"]["trace"][
        "trace_id"] == transaction.trace_id

    if task_fails:
        assert execution_event["contexts"]["trace"][
            "status"] == "internal_error"
    else:
        assert execution_event["contexts"]["trace"]["status"] == "ok"

    assert execution_event["spans"] == []
    assert submission_event["spans"] == [{
        u"description":
        u"dummy_task",
        u"op":
        "celery.submit",
        u"parent_span_id":
        submission_event["contexts"]["trace"]["span_id"],
        u"same_process_as_parent":
        True,
        u"span_id":
        submission_event["spans"][0]["span_id"],
        u"start_timestamp":
        submission_event["spans"][0]["start_timestamp"],
        u"timestamp":
        submission_event["spans"][0]["timestamp"],
        u"trace_id":
        text_type(transaction.trace_id),
    }]
Esempio n. 4
0
def test_transport_option(monkeypatch):
    dsn = "https://[email protected]/123"
    dsn2 = "https://[email protected]/124"
    assert str(Client(dsn=dsn).dsn) == dsn
    assert Client().dsn is None

    monkeypatch.setenv("SENTRY_DSN", dsn)
    transport = Transport({"dsn": dsn2})
    assert text_type(transport.parsed_dsn) == dsn2
    assert str(Client(transport=transport).dsn) == dsn
Esempio n. 5
0
    def _prepare_event(self, event, hint, scope):
        if event.get("timestamp") is None:
            event["timestamp"] = datetime.utcnow()

        if scope is not None:
            event = scope.apply_to_event(event, hint)
            if event is None:
                return

        if (
            self.options["attach_stacktrace"]
            and "exception" not in event
            and "stacktrace" not in event
            and "threads" not in event
        ):
            with capture_internal_exceptions():
                event["threads"] = [
                    {
                        "stacktrace": current_stacktrace(),
                        "crashed": False,
                        "current": True,
                    }
                ]

        for key in "release", "environment", "server_name", "dist":
            if event.get(key) is None and self.options[key] is not None:
                event[key] = text_type(self.options[key]).strip()
        if event.get("sdk") is None:
            sdk_info = dict(SDK_INFO)
            sdk_info["integrations"] = sorted(self.integrations.keys())
            event["sdk"] = sdk_info

        if event.get("platform") is None:
            event["platform"] = "python"

        event = handle_in_app(
            event, self.options["in_app_exclude"], self.options["in_app_include"]
        )

        before_send = self.options["before_send"]
        if before_send is not None:
            with capture_internal_exceptions():
                new_event = before_send(event, hint)
            if new_event is None:
                logger.info("before send dropped event (%s)", event)
            event = new_event

        # Postprocess the event in the very end so that annotated types do
        # generally not surface in before_send
        if event is not None:
            strip_event_mut(event)
            event = flatten_metadata(event)
            event = convert_types(event)

        return event
Esempio n. 6
0
    def _annotate(**meta):
        # type: (**Any) -> None
        while len(meta_stack) <= len(path):
            try:
                segment = path[len(meta_stack) - 1]
                node = meta_stack[-1].setdefault(text_type(segment), {})
            except IndexError:
                node = {}

            meta_stack.append(node)

        meta_stack[-1].setdefault("", {}).update(meta)
Esempio n. 7
0
    def sanitize(self, item, value):
        if value is None:
            return

        if not item:  # key can be a NoneType
            return value

        # Just in case we have bytes here, we want to make them into text
        # properly without failing so we can perform our check.
        if isinstance(item, bytes):
            item = item.decode('utf-8', 'replace')
        else:
            item = text_type(item)

        item = item.lower()
        for key in self.sanitize_keys:
            if key in item:
                # store mask as a fixed length for security
                return self.MASK
        return value
Esempio n. 8
0
def convert_types(obj):
    # type: (Any) -> Any
    if obj is None:
        return None
    if obj is CYCLE_MARKER:
        return u"<cyclic>"
    if isinstance(obj, datetime):
        return text_type(obj.strftime("%Y-%m-%dT%H:%M:%SZ"))
    if isinstance(obj, Mapping):
        return {k: convert_types(v) for k, v in obj.items()}
    if isinstance(obj, Sequence) and not isinstance(obj, (text_type, bytes)):
        return [convert_types(v) for v in obj]
    if isinstance(obj, AnnotatedValue):
        return AnnotatedValue(convert_types(obj.value), obj.metadata)

    if not isinstance(obj, string_types + number_types):
        return safe_repr(obj)
    if isinstance(obj, bytes):
        return obj.decode("utf-8", "replace")

    return obj
Esempio n. 9
0
    def _serialize_node_impl(self, obj, max_depth, max_breadth):
        # type: (Any, Optional[int], Optional[int]) -> Any
        if max_depth is None and max_breadth is None and self.meta_node.is_databag(
        ):
            max_depth = self.meta_node._depth + MAX_DATABAG_DEPTH
            max_breadth = self.meta_node._depth + MAX_DATABAG_BREADTH

        if max_depth is None:
            remaining_depth = None
        else:
            remaining_depth = max_depth - self.meta_node._depth

        obj = _flatten_annotated(obj, self.meta_node)

        if remaining_depth is not None and remaining_depth <= 0:
            self.meta_node.annotate(rem=[["!limit", "x"]])
            if self.meta_node.is_databag():
                return _flatten_annotated(strip_string(safe_repr(obj)),
                                          self.meta_node)
            return None

        if self.meta_node.is_databag():
            hints = {"memo": self.memo, "remaining_depth": remaining_depth}
            for processor in global_repr_processors:
                with capture_internal_exceptions():
                    result = processor(obj, hints)
                    if result is not NotImplemented:
                        return _flatten_annotated(result, self.meta_node)

        if isinstance(obj, Mapping):
            # Create temporary list here to avoid calling too much code that
            # might mutate our dictionary while we're still iterating over it.
            items = []
            for i, (k, v) in enumerate(iteritems(obj)):
                if max_breadth is not None and i >= max_breadth:
                    self.meta_node.annotate(len=max_breadth)
                    break

                items.append((k, v))

            rv_dict = {}  # type: Dict[Any, Any]
            for k, v in items:
                k = text_type(k)

                with self.enter(k):
                    v = self._serialize_node(v,
                                             max_depth=max_depth,
                                             max_breadth=max_breadth)
                    if v is not None:
                        rv_dict[k] = v

            return rv_dict
        elif isinstance(obj, Sequence) and not isinstance(obj, string_types):
            rv_list = []  # type: List[Any]
            for i, v in enumerate(obj):
                if max_breadth is not None and i >= max_breadth:
                    self.meta_node.annotate(len=max_breadth)
                    break

                with self.enter(i):
                    rv_list.append(
                        self._serialize_node(v,
                                             max_depth=max_depth,
                                             max_breadth=max_breadth))

            return rv_list

        if self.meta_node.should_repr_strings():
            obj = safe_repr(obj)
        else:
            if obj is None or isinstance(obj, (bool, number_types)):
                return obj

            if isinstance(obj, datetime):
                return text_type(obj.strftime("%Y-%m-%dT%H:%M:%SZ"))

            if isinstance(obj, bytes):
                obj = obj.decode("utf-8", "replace")

            if not isinstance(obj, string_types):
                obj = safe_repr(obj)

        return _flatten_annotated(strip_string(obj), self.meta_node)
Esempio n. 10
0
    def _prepare_event(
            self,
            event,  # type: Event
            hint,  # type: Optional[Hint]
            scope,  # type: Optional[Scope]
    ):
        # type: (...) -> Optional[Event]
        if event.get("timestamp") is None:
            event["timestamp"] = datetime.utcnow()

        hint = dict(hint or ())  # type: Hint

        if scope is not None:
            event_ = scope.apply_to_event(event, hint)
            if event_ is None:
                return None
            event = event_

        if (self.options["attach_stacktrace"] and "exception" not in event
                and "stacktrace" not in event and "threads" not in event):
            with capture_internal_exceptions():
                event["threads"] = {
                    "values": [{
                        "stacktrace":
                        current_stacktrace(self.options["with_locals"]),
                        "crashed":
                        False,
                        "current":
                        True,
                    }]
                }

        for key in "release", "environment", "server_name", "dist":
            if event.get(key) is None and self.options[
                    key] is not None:  # type: ignore
                event[key] = text_type(
                    self.options[key]).strip()  # type: ignore
        if event.get("sdk") is None:
            sdk_info = dict(SDK_INFO)
            sdk_info["integrations"] = sorted(self.integrations.keys())
            event["sdk"] = sdk_info

        if event.get("platform") is None:
            event["platform"] = "python"

        event = handle_in_app(event, self.options["in_app_exclude"],
                              self.options["in_app_include"])

        # Postprocess the event here so that annotated types do
        # generally not surface in before_send
        if event is not None:
            event = Serializer().serialize_event(event)

        before_send = self.options["before_send"]
        if before_send is not None:
            new_event = None
            with capture_internal_exceptions():
                new_event = before_send(event, hint or {})
            if new_event is None:
                logger.info("before send dropped event (%s)", event)
            event = new_event  # type: ignore

        return event
Esempio n. 11
0
    def _serialize_node_impl(
        obj, is_databag, should_repr_strings, remaining_depth, remaining_breadth
    ):
        # type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
        if should_repr_strings is None:
            should_repr_strings = _should_repr_strings()

        if is_databag is None:
            is_databag = _is_databag()

        if is_databag and remaining_depth is None:
            remaining_depth = MAX_DATABAG_DEPTH
        if is_databag and remaining_breadth is None:
            remaining_breadth = MAX_DATABAG_BREADTH

        obj = _flatten_annotated(obj)

        if remaining_depth is not None and remaining_depth <= 0:
            _annotate(rem=[["!limit", "x"]])
            if is_databag:
                return _flatten_annotated(strip_string(safe_repr(obj)))
            return None

        if is_databag and global_repr_processors:
            hints = {"memo": memo, "remaining_depth": remaining_depth}
            for processor in global_repr_processors:
                result = processor(obj, hints)
                if result is not NotImplemented:
                    return _flatten_annotated(result)

        if obj is None or isinstance(obj, (bool, number_types)):
            return obj if not should_repr_strings else safe_repr(obj)

        elif isinstance(obj, datetime):
            return (
                text_type(obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ"))
                if not should_repr_strings
                else safe_repr(obj)
            )

        elif isinstance(obj, Mapping):
            # Create temporary copy here to avoid calling too much code that
            # might mutate our dictionary while we're still iterating over it.
            obj = dict(iteritems(obj))

            rv_dict = {}
            i = 0

            for k, v in iteritems(obj):
                if remaining_breadth is not None and i >= remaining_breadth:
                    _annotate(len=len(obj))
                    break

                str_k = text_type(k)
                v = _serialize_node(
                    v,
                    segment=str_k,
                    should_repr_strings=should_repr_strings,
                    is_databag=is_databag,
                    remaining_depth=remaining_depth - 1
                    if remaining_depth is not None
                    else None,
                    remaining_breadth=remaining_breadth,
                )
                if v is not None:
                    rv_dict[str_k] = v
                    i += 1

            return rv_dict

        elif not isinstance(obj, serializable_str_types) and isinstance(obj, Sequence):
            rv_list = []

            for i, v in enumerate(obj):
                if remaining_breadth is not None and i >= remaining_breadth:
                    _annotate(len=len(obj))
                    break

                rv_list.append(
                    _serialize_node(
                        v,
                        segment=i,
                        should_repr_strings=should_repr_strings,
                        is_databag=is_databag,
                        remaining_depth=remaining_depth - 1
                        if remaining_depth is not None
                        else None,
                        remaining_breadth=remaining_breadth,
                    )
                )

            return rv_list

        if should_repr_strings:
            obj = safe_repr(obj)
        else:
            if isinstance(obj, bytes):
                obj = obj.decode("utf-8", "replace")

            if not isinstance(obj, string_types):
                obj = safe_repr(obj)

        return _flatten_annotated(strip_string(obj))
Esempio n. 12
0
    def _serialize_node_impl(obj, is_databag, should_repr_strings,
                             remaining_depth, remaining_breadth):
        # type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
        if should_repr_strings is None:
            should_repr_strings = _should_repr_strings()

        if is_databag is None:
            is_databag = _is_databag()

        if is_databag and remaining_depth is None:
            remaining_depth = MAX_DATABAG_DEPTH
        if is_databag and remaining_breadth is None:
            remaining_breadth = MAX_DATABAG_BREADTH

        obj = _flatten_annotated(obj)

        if remaining_depth is not None and remaining_depth <= 0:
            _annotate(rem=[["!limit", "x"]])
            if is_databag:
                return _flatten_annotated(strip_string(safe_repr(obj)))
            return None

        if is_databag and global_repr_processors:
            hints = {"memo": memo, "remaining_depth": remaining_depth}
            for processor in global_repr_processors:
                result = processor(obj, hints)
                if result is not NotImplemented:
                    return _flatten_annotated(result)

        if obj is None or isinstance(obj, (bool, number_types)):
            if should_repr_strings or (isinstance(obj, float) and
                                       (math.isinf(obj) or math.isnan(obj))):
                return safe_repr(obj)
            else:
                return obj

        elif isinstance(obj, datetime):
            return (text_type(format_timestamp(obj))
                    if not should_repr_strings else safe_repr(obj))

        elif isinstance(obj, Mapping):
            # Create temporary copy here to avoid calling too much code that
            # might mutate our dictionary while we're still iterating over it.
            obj = dict(iteritems(obj))

            rv_dict = {}  # type: Dict[str, Any]
            i = 0

            for k, v in iteritems(obj):
                if remaining_breadth is not None and i >= remaining_breadth:
                    _annotate(len=len(obj))
                    break

                str_k = text_type(k)
                v = _serialize_node(
                    v,
                    segment=str_k,
                    should_repr_strings=should_repr_strings,
                    is_databag=is_databag,
                    remaining_depth=remaining_depth -
                    1 if remaining_depth is not None else None,
                    remaining_breadth=remaining_breadth,
                )
                rv_dict[str_k] = v
                i += 1

            return rv_dict

        elif not isinstance(obj, serializable_str_types) and isinstance(
                obj, (Set, Sequence)):
            rv_list = []

            for i, v in enumerate(obj):
                if remaining_breadth is not None and i >= remaining_breadth:
                    _annotate(len=len(obj))
                    break

                rv_list.append(
                    _serialize_node(
                        v,
                        segment=i,
                        should_repr_strings=should_repr_strings,
                        is_databag=is_databag,
                        remaining_depth=remaining_depth -
                        1 if remaining_depth is not None else None,
                        remaining_breadth=remaining_breadth,
                    ))

            return rv_list

        if should_repr_strings:
            obj = safe_repr(obj)
        else:
            if isinstance(obj, bytes):
                obj = obj.decode("utf-8", "replace")

            if not isinstance(obj, string_types):
                obj = safe_repr(obj)

        # Allow span descriptions to be longer than other strings.
        #
        # For database auto-instrumented spans, the description contains
        # potentially long SQL queries that are most useful when not truncated.
        # Because arbitrarily large events may be discarded by the server as a
        # protection mechanism, we dynamically limit the description length
        # later in _truncate_span_descriptions.
        if (smart_transaction_trimming and len(path) == 3
                and path[0] == "spans" and path[-1] == "description"):
            span_description_bytes.append(len(obj))
            return obj
        return _flatten_annotated(strip_string(obj))
Esempio n. 13
0
def safe_str(value):
    # type: (Any) -> str
    try:
        return text_type(value)
    except Exception:
        return safe_repr(value)
Esempio n. 14
0
    def _serialize_node_impl(
        obj, max_depth, max_breadth, is_databag, should_repr_strings
    ):
        # type: (Any, Optional[int], Optional[int], Optional[bool], Optional[bool]) -> Any
        if not should_repr_strings:
            should_repr_strings = (
                _startswith_path(
                    ("exception", "values", None, "stacktrace", "frames", None, "vars")
                )
                or _startswith_path(
                    ("threads", "values", None, "stacktrace", "frames", None, "vars")
                )
                or _startswith_path(("stacktrace", "frames", None, "vars"))
            )

        if obj is None or isinstance(obj, (bool, number_types)):
            return obj if not should_repr_strings else safe_repr(obj)

        if isinstance(obj, datetime):
            return (
                text_type(obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ"))
                if not should_repr_strings
                else safe_repr(obj)
            )

        if not is_databag:
            is_databag = (
                should_repr_strings
                or _startswith_path(("request", "data"))
                or _startswith_path(("breadcrumbs", None))
                or _startswith_path(("extra",))
            )

        cur_depth = len(path)
        if max_depth is None and max_breadth is None and is_databag:
            max_depth = cur_depth + MAX_DATABAG_DEPTH
            max_breadth = cur_depth + MAX_DATABAG_BREADTH

        if max_depth is None:
            remaining_depth = None
        else:
            remaining_depth = max_depth - cur_depth

        obj = _flatten_annotated(obj)

        if remaining_depth is not None and remaining_depth <= 0:
            _annotate(rem=[["!limit", "x"]])
            if is_databag:
                return _flatten_annotated(strip_string(safe_repr(obj)))
            return None

        if global_repr_processors and is_databag:
            hints = {"memo": memo, "remaining_depth": remaining_depth}
            for processor in global_repr_processors:
                result = processor(obj, hints)
                if result is not NotImplemented:
                    return _flatten_annotated(result)

        if isinstance(obj, Mapping):
            # Create temporary copy here to avoid calling too much code that
            # might mutate our dictionary while we're still iterating over it.
            if max_breadth is not None and len(obj) >= max_breadth:
                rv_dict = dict(itertools.islice(iteritems(obj), None, max_breadth))
                _annotate(len=len(obj))
            else:
                if type(obj) is dict:
                    rv_dict = dict(obj)
                else:
                    rv_dict = dict(iteritems(obj))

            for k in list(rv_dict):
                str_k = text_type(k)
                v = _serialize_node(
                    rv_dict.pop(k),
                    max_depth=max_depth,
                    max_breadth=max_breadth,
                    segment=str_k,
                    should_repr_strings=should_repr_strings,
                    is_databag=is_databag,
                )
                if v is not None:
                    rv_dict[str_k] = v

            return rv_dict
        elif not isinstance(obj, string_types) and isinstance(obj, Sequence):
            if max_breadth is not None and len(obj) >= max_breadth:
                rv_list = list(obj)[:max_breadth]
                _annotate(len=len(obj))
            else:
                rv_list = list(obj)

            for i in range(len(rv_list)):
                rv_list[i] = _serialize_node(
                    rv_list[i],
                    max_depth=max_depth,
                    max_breadth=max_breadth,
                    segment=i,
                    should_repr_strings=should_repr_strings,
                    is_databag=is_databag,
                )

            return rv_list

        if should_repr_strings:
            obj = safe_repr(obj)
        else:
            if isinstance(obj, bytes):
                obj = obj.decode("utf-8", "replace")

            if not isinstance(obj, string_types):
                obj = safe_repr(obj)

        return _flatten_annotated(strip_string(obj))
Esempio n. 15
0
def safe_str(value):
    try:
        return text_type(value)
    except Exception:
        return safe_repr(value)
Esempio n. 16
0
def to_string(value):
    try:
        return text_type(value)
    except UnicodeDecodeError:
        return repr(value)[1:-1]
Esempio n. 17
0
    def _prepare_event(
        self,
        event,  # type: Event
        hint,  # type: Hint
        scope,  # type: Optional[Scope]
    ):
        # type: (...) -> Optional[Event]

        if event.get("timestamp") is None:
            event["timestamp"] = datetime.utcnow()

        if scope is not None:
            is_transaction = event.get("type") == "transaction"
            event_ = scope.apply_to_event(event, hint)

            # one of the event/error processors returned None
            if event_ is None:
                if self.transport:
                    self.transport.record_lost_event(
                        "event_processor",
                        data_category=("transaction" if is_transaction else "error"),
                    )
                return None

            event = event_

        if (
            self.options["attach_stacktrace"]
            and "exception" not in event
            and "stacktrace" not in event
            and "threads" not in event
        ):
            with capture_internal_exceptions():
                event["threads"] = {
                    "values": [
                        {
                            "stacktrace": current_stacktrace(
                                self.options["with_locals"],
                                self.options["with_source_context"],
                            ),
                            "crashed": False,
                            "current": True,
                        }
                    ]
                }

        for key in "release", "environment", "server_name", "dist":
            if event.get(key) is None and self.options[key] is not None:
                event[key] = text_type(self.options[key]).strip()
        if event.get("sdk") is None:
            sdk_info = dict(SDK_INFO)
            sdk_info["integrations"] = sorted(self.integrations.keys())
            event["sdk"] = sdk_info

        if event.get("platform") is None:
            event["platform"] = "python"

        event = handle_in_app(
            event, self.options["in_app_exclude"], self.options["in_app_include"]
        )

        # Postprocess the event here so that annotated types do
        # generally not surface in before_send
        if event is not None:
            event = serialize(
                event,
                smart_transaction_trimming=self.options["_experiments"].get(
                    "smart_transaction_trimming"
                ),
            )

        before_send = self.options["before_send"]
        if before_send is not None and event.get("type") != "transaction":
            new_event = None
            with capture_internal_exceptions():
                new_event = before_send(event, hint or {})
            if new_event is None:
                logger.info("before send dropped event (%s)", event)
                if self.transport:
                    self.transport.record_lost_event(
                        "before_send", data_category="error"
                    )
            event = new_event  # type: ignore

        return event