Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions drift/core/drift_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,6 @@ def mark_app_as_ready(self) -> None:
if self._td_span_processor:
self._td_span_processor.update_app_ready(True)

logger.debug("Application marked as ready")

if self.mode == TuskDriftMode.REPLAY:
logger.debug("Replay mode active - ready to serve mocked responses")
elif self.mode == TuskDriftMode.RECORD:
Expand Down
49 changes: 44 additions & 5 deletions drift/instrumentation/django/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,31 +49,42 @@ def _resolve_http_transforms(
@override
def patch(self, module: ModuleType) -> None:
"""Patch Django by injecting middleware."""
if not self._try_inject_middleware():
# Settings not configured yet — defer injection until django.setup() runs
self._defer_middleware_injection()

def _try_inject_middleware(self) -> bool:
"""Attempt to inject DriftMiddleware into Django settings.

Returns:
True if middleware was injected (or already present), False if
settings are not yet configured and injection should be deferred.
"""
global _middleware_injected

if _middleware_injected:
logger.debug("Middleware already injected, skipping")
return
return True

try:
from django.conf import settings

if not settings.configured:
logger.warning("Django settings not configured, cannot inject middleware")
return
logger.debug("Django settings not configured yet, will defer middleware injection")
return False

middleware_setting = self._get_middleware_setting(settings)
if not middleware_setting:
logger.warning("Could not find middleware setting, cannot inject")
return
return True # Don't retry — this won't change

current_middleware = list(getattr(settings, middleware_setting, []))

middleware_path = "drift.instrumentation.django.middleware.DriftMiddleware"
if middleware_path in current_middleware:
logger.debug("DriftMiddleware already in settings, skipping injection")
_middleware_injected = True
return
return True

# Insert at position 0 to capture all requests
current_middleware.insert(0, middleware_path)
Expand All @@ -89,11 +100,39 @@ def patch(self, module: ModuleType) -> None:
self._force_database_reconnect()

print("Django instrumentation applied")
return True

except ImportError as e:
logger.warning(f"Could not import Django settings: {e}")
return True # Don't retry on import errors
except Exception as e:
logger.error(f"Failed to inject middleware: {e}", exc_info=True)
return True # Don't retry on unexpected errors

def _defer_middleware_injection(self) -> None:
"""Monkey-patch django.setup() to inject middleware after settings are configured.

When TuskDrift.initialize() runs before DJANGO_SETTINGS_MODULE is set
(common in manage.py where the SDK init is the first import), Django
settings aren't available yet. This defers injection to run after
django.setup() completes, which is when settings are guaranteed to be
configured.
"""
import django

original_setup = django.setup

def patched_setup(*args, **kwargs):
# Restore original setup first to avoid re-entrance
django.setup = original_setup
# Run the original django.setup()
result = original_setup(*args, **kwargs)
# Now settings are configured — inject middleware
self._try_inject_middleware()
return result
Comment thread
sohankshirsagar marked this conversation as resolved.
Outdated

django.setup = patched_setup
logger.debug("Deferred middleware injection to django.setup()")

def _force_database_reconnect(self) -> None:
"""Force Django to close and recreate database connections."""
Expand Down
3 changes: 3 additions & 0 deletions drift/instrumentation/django/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def __call__(self, request: HttpRequest) -> HttpResponse:
if sdk.mode == TuskDriftMode.DISABLED:
return self.get_response(request)

if not sdk.app_ready:
sdk.mark_app_as_ready()

# REPLAY mode - handle trace ID extraction and context setup
if sdk.mode == TuskDriftMode.REPLAY:
return self._handle_replay_request(request, sdk)
Expand Down
3 changes: 3 additions & 0 deletions drift/instrumentation/fastapi/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ async def _handle_request(
if sdk.mode == TuskDriftMode.DISABLED:
return await original_call(app, scope, receive, send)

if not sdk.app_ready:
sdk.mark_app_as_ready()

# REPLAY mode - handle trace ID extraction and context setup
if sdk.mode == TuskDriftMode.REPLAY:
return await _handle_replay_request(
Expand Down
3 changes: 3 additions & 0 deletions drift/instrumentation/wsgi/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ def original_call() -> Iterable[bytes]:
if sdk.mode == TuskDriftMode.DISABLED:
return original_call()

if not sdk.app_ready:
sdk.mark_app_as_ready()

# REPLAY mode: requires trace ID header
if sdk.mode == TuskDriftMode.REPLAY:
return _handle_replay_request(
Expand Down
Loading