diff --git a/sentry_sdk/profiler/utils.py b/sentry_sdk/profiler/utils.py index e78ea54256..3554cddb5d 100644 --- a/sentry_sdk/profiler/utils.py +++ b/sentry_sdk/profiler/utils.py @@ -89,7 +89,7 @@ def get_frame_name(frame): and co_varnames[0] == "self" and "self" in frame.f_locals ): - for cls in frame.f_locals["self"].__class__.__mro__: + for cls in type(frame.f_locals["self"]).__mro__: if name in cls.__dict__: return "{}.{}".format(cls.__name__, name) except (AttributeError, ValueError): diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index c8282412ea..0e3f700105 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -1,6 +1,8 @@ +import inspect import json import os import re +import sys import pytest from functools import partial from unittest.mock import patch @@ -12,6 +14,7 @@ from django.core.management import execute_from_command_line from django.db.utils import OperationalError, ProgrammingError, DataError from django.http.request import RawPostDataException +from django.utils.functional import SimpleLazyObject try: from django.urls import reverse @@ -29,6 +32,7 @@ ) from sentry_sdk.integrations.django.signals_handlers import _get_receiver_name from sentry_sdk.integrations.executing import ExecutingIntegration +from sentry_sdk.profiler.utils import get_frame_name from sentry_sdk.tracing import Span from tests.conftest import unpack_werkzeug_response from tests.integrations.django.myapp.wsgi import application @@ -1295,3 +1299,47 @@ def test_ensures_no_spotlight_middleware_when_no_spotlight( added = frozenset(settings.MIDDLEWARE) ^ original_middleware assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added + + +def test_get_frame_name_when_in_lazy_object(): + allowed_to_init = False + + class SimpleLazyObjectWrapper(SimpleLazyObject): + def unproxied_method(self): + """ + For testing purposes. We inject a method on the SimpleLazyObject + class so if python is executing this method, we should get + this class instead of the wrapped class and avoid evaluating + the wrapped object too early. + """ + return inspect.currentframe() + + class GetFrame: + def __init__(self): + assert allowed_to_init, "GetFrame not permitted to initialize yet" + + def proxied_method(self): + """ + For testing purposes. We add an proxied method on the instance + class so if python is executing this method, we should get + this class instead of the wrapper class. + """ + return inspect.currentframe() + + instance = SimpleLazyObjectWrapper(lambda: GetFrame()) + + assert get_frame_name(instance.unproxied_method()) == ( + "SimpleLazyObjectWrapper.unproxied_method" + if sys.version_info < (3, 11) + else "test_get_frame_name_when_in_lazy_object..SimpleLazyObjectWrapper.unproxied_method" + ) + + # Now that we're about to access an instance method on the wrapped class, + # we should permit initializing it + allowed_to_init = True + + assert get_frame_name(instance.proxied_method()) == ( + "GetFrame.proxied_method" + if sys.version_info < (3, 11) + else "test_get_frame_name_when_in_lazy_object..GetFrame.proxied_method" + )