Skip to content

Commit a9bcd78

Browse files
committed
remove code attribute cache from django patches
1 parent 52e31e5 commit a9bcd78

File tree

2 files changed

+29
-114
lines changed

2 files changed

+29
-114
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_django_patches.py

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,13 @@ def _apply_django_code_attributes_patch() -> None: # pylint: disable=too-many-s
2828
code attributes to the current span when a view function is about to be executed.
2929
3030
The patch includes:
31-
1. Caching mechanism to avoid re-processing the same view functions
32-
2. Support for class-based views by extracting the actual HTTP method handler
33-
3. Automatic addition of code.function.name, code.file.path, and code.line.number
34-
4. Graceful error handling and cleanup during uninstrument
31+
1. Support for class-based views by extracting the actual HTTP method handler
32+
2. Automatic addition of code.function.name, code.file.path, and code.line.number
33+
3. Graceful error handling and cleanup during uninstrument
3534
"""
3635
try:
3736
# Import Django instrumentation classes and AWS code correlation function
3837
from amazon.opentelemetry.distro.code_correlation import ( # pylint: disable=import-outside-toplevel
39-
CODE_FILE_PATH,
40-
CODE_FUNCTION_NAME,
41-
CODE_LINE_NUMBER,
4238
add_code_attributes_to_span,
4339
)
4440
from opentelemetry.instrumentation.django import DjangoInstrumentor # pylint: disable=import-outside-toplevel
@@ -61,10 +57,6 @@ def _patch_django_middleware():
6157
if original_process_view is None:
6258
original_process_view = _DjangoMiddleware.process_view
6359

64-
# Add code cache to the middleware class
65-
if not hasattr(_DjangoMiddleware, "_code_cache"):
66-
_DjangoMiddleware._code_cache = {}
67-
6860
def patched_process_view(
6961
self, request, view_func, *args, **kwargs
7062
): # pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches
@@ -90,53 +82,12 @@ def patched_process_view(
9082
handler = getattr(view_class, method_name, None) or view_class
9183
target = handler
9284

93-
# Try to get attributes from cache first
94-
if target in self._code_cache:
95-
# Directly set attributes from cache
96-
for key, value in self._code_cache[target].items():
97-
if value is not None: # Only set non-None values
98-
span.set_attribute(key, value)
99-
_logger.debug(
100-
"Added cached code attributes to span for Django view: %s",
101-
getattr(target, "__name__", str(target)),
102-
)
103-
else:
104-
# Call the existing add_code_attributes_to_span function
105-
add_code_attributes_to_span(span, target)
106-
107-
# Read the attributes that were just set and cache them
108-
# Note: span.attributes might not be available immediately,
109-
# so we extract the attributes in a safer way
110-
cached_attrs = {}
111-
try:
112-
# Try to get attributes if they exist
113-
if hasattr(span, "attributes") and span.attributes:
114-
cached_attrs = {
115-
CODE_FUNCTION_NAME: span.attributes.get(CODE_FUNCTION_NAME),
116-
CODE_FILE_PATH: span.attributes.get(CODE_FILE_PATH),
117-
CODE_LINE_NUMBER: span.attributes.get(CODE_LINE_NUMBER),
118-
}
119-
else:
120-
# Fallback: extract from the target directly
121-
import inspect # pylint: disable=import-outside-toplevel
122-
123-
if hasattr(target, "__name__"):
124-
cached_attrs[CODE_FUNCTION_NAME] = target.__name__
125-
if hasattr(target, "__code__") and target.__code__:
126-
cached_attrs[CODE_FILE_PATH] = target.__code__.co_filename
127-
cached_attrs[CODE_LINE_NUMBER] = target.__code__.co_firstlineno
128-
elif inspect.isclass(target):
129-
cached_attrs[CODE_FUNCTION_NAME] = target.__name__
130-
cached_attrs[CODE_FILE_PATH] = inspect.getfile(target)
131-
except Exception as cache_exc: # pylint: disable=broad-exception-caught
132-
_logger.debug("Failed to cache code attributes: %s", cache_exc)
133-
134-
# Cache the attributes for future use
135-
self._code_cache[target] = cached_attrs
136-
_logger.debug(
137-
"Added and cached code attributes to span for Django view: %s",
138-
getattr(target, "__name__", str(target)),
139-
)
85+
# Call the existing add_code_attributes_to_span function
86+
add_code_attributes_to_span(span, target)
87+
_logger.debug(
88+
"Added code attributes to span for Django view: %s",
89+
getattr(target, "__name__", str(target)),
90+
)
14091
except Exception as exc: # pylint: disable=broad-exception-caught
14192
# Don't let code attributes addition break the request processing
14293
_logger.warning("Failed to add code attributes to Django span: %s", exc)
@@ -158,9 +109,6 @@ def _unpatch_django_middleware():
158109

159110
if original_process_view is not None:
160111
_DjangoMiddleware.process_view = original_process_view
161-
# Clean up the code cache
162-
if hasattr(_DjangoMiddleware, "_code_cache"):
163-
delattr(_DjangoMiddleware, "_code_cache")
164112
_logger.debug("Django middleware process_view restored successfully")
165113

166114
except Exception as exc: # pylint: disable=broad-exception-caught

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/patches/test_django_patches.py

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,11 @@ def test_view(request):
186186
instrumentor.instrument()
187187

188188
try:
189-
# Create a mock span without attributes (to trigger fallback logic in lines 122-132)
189+
# Create a mock span
190190
from unittest.mock import Mock
191191

192192
mock_span = Mock()
193193
mock_span.is_recording.return_value = True
194-
mock_span.attributes = None # This will trigger the fallback path
195-
mock_span.set_attribute = Mock()
196194

197195
# Create Django request
198196
request = self.factory.get("/test/")
@@ -207,42 +205,24 @@ def test_view(request):
207205
request.META[middleware_key] = "test_activation"
208206
request.META[span_key] = mock_span
209207

210-
# Verify the middleware has the code cache attribute after patching
211-
self.assertTrue(hasattr(_DjangoMiddleware, "_code_cache"))
212-
213-
# Clear any existing cache
214-
_DjangoMiddleware._code_cache.clear()
215-
216208
# Call process_view method which should trigger the patch
217209
result = middleware.process_view(request, test_view, [], {})
218210

219211
# The result should be None (original process_view returns None)
220212
self.assertIsNone(result)
221213

222-
# Verify span methods were called
214+
# Verify span methods were called (this confirms the patched code ran)
223215
mock_span.is_recording.assert_called()
224216

225-
# Check that code attributes were added to the span via fallback logic
226-
# This should have triggered the fallback code in lines 122-132
227-
cache = _DjangoMiddleware._code_cache
228-
self.assertIn(test_view, cache)
229-
230-
# Verify cache contains expected code attribute keys (from fallback logic)
231-
cached_attrs = cache[test_view]
232-
self.assertIn("code.function.name", cached_attrs)
233-
self.assertEqual(cached_attrs.get("code.function.name"), "test_view")
234-
self.assertIn("code.file.path", cached_attrs)
235-
self.assertIn("code.line.number", cached_attrs)
236-
237-
# Verify span.set_attribute was called with cached attributes
238-
mock_span.set_attribute.assert_called()
217+
# Test passes if no exceptions are raised and the method returns correctly
218+
# The main goal is to ensure the removal of _code_cache doesn't break functionality
239219

240220
finally:
241221
# Clean up instrumentation
242222
instrumentor.uninstrument()
243223

244224
def test_django_class_based_view_patch_process_view(self, mock_get_status):
245-
"""Test Django patch with class-based view to cover lines 128-132."""
225+
"""Test Django patch with class-based view to test handler targeting logic."""
246226

247227
# Define a class-based Django view
248228
class TestClassView:
@@ -251,6 +231,13 @@ class TestClassView:
251231
def get(self, request):
252232
return HttpResponse("Hello from class view")
253233

234+
# Create a mock view function that mimics Django's class-based view structure
235+
def mock_view_func(request):
236+
return HttpResponse("Mock response")
237+
238+
# Add view_class attribute to simulate Django's class-based view wrapper
239+
mock_view_func.view_class = TestClassView
240+
254241
# Apply the Django code attributes patch
255242
_apply_django_code_attributes_patch()
256243

@@ -259,15 +246,13 @@ def get(self, request):
259246
instrumentor.instrument()
260247

261248
try:
262-
# Create a mock span without attributes (to trigger fallback logic)
249+
# Create a mock span
263250
from unittest.mock import Mock
264251

265252
mock_span = Mock()
266253
mock_span.is_recording.return_value = True
267-
mock_span.attributes = None # This will trigger the fallback path
268-
mock_span.set_attribute = Mock()
269254

270-
# Create Django request
255+
# Create Django request with GET method
271256
request = self.factory.get("/test/")
272257

273258
# Create middleware instance
@@ -280,36 +265,18 @@ def get(self, request):
280265
request.META[middleware_key] = "test_activation"
281266
request.META[span_key] = mock_span
282267

283-
# Clear any existing cache
284-
_DjangoMiddleware._code_cache.clear()
285-
286-
# Create a class instance to use as target
287-
# This should trigger the inspect.isclass() path in lines 128-132
288-
test_class = TestClassView
289-
290-
# Call process_view method with the class (not instance) as view_func
291-
# This should trigger the fallback logic with inspect.isclass(target) = True
292-
result = middleware.process_view(request, test_class, [], {})
268+
# Call process_view method with the class-based view function
269+
# This should trigger the class-based view logic where it extracts the handler
270+
result = middleware.process_view(request, mock_view_func, [], {})
293271

294272
# The result should be None (original process_view returns None)
295273
self.assertIsNone(result)
296274

297-
# Verify span methods were called
275+
# Verify span methods were called (this confirms the patched code ran)
298276
mock_span.is_recording.assert_called()
299277

300-
# Check that code attributes were added to the span via fallback logic
301-
# This should have triggered the inspect.isclass() code in lines 128-132
302-
cache = _DjangoMiddleware._code_cache
303-
self.assertIn(test_class, cache)
304-
305-
# Verify cache contains expected code attribute keys (from inspect.isclass fallback)
306-
cached_attrs = cache[test_class]
307-
self.assertIn("code.function.name", cached_attrs)
308-
self.assertEqual(cached_attrs.get("code.function.name"), "TestClassView")
309-
self.assertIn("code.file.path", cached_attrs)
310-
311-
# Verify span.set_attribute was called with cached attributes
312-
mock_span.set_attribute.assert_called()
278+
# Test passes if no exceptions are raised and the method returns correctly
279+
# The main goal is to ensure the removal of _code_cache doesn't break functionality
313280

314281
finally:
315282
# Clean up instrumentation

0 commit comments

Comments
 (0)