Skip to content

Commit 85b6cbd

Browse files
authored
Dev2 (#3)
* fix trace propagation * add django, abstract wsgi
1 parent 8fd9f54 commit 85b6cbd

12 files changed

Lines changed: 1602 additions & 206 deletions

File tree

drift/instrumentation/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
"""Instrumentation module for Drift Python SDK."""
22

33
from .base import InstrumentationBase
4+
from .django import DjangoInstrumentation
45
from .registry import register_patch, install_hooks, patch_instances_via_gc
56

6-
__all__ = ["InstrumentationBase", "register_patch", "install_hooks", "patch_instances_via_gc"]
7+
__all__ = [
8+
"InstrumentationBase",
9+
"DjangoInstrumentation",
10+
"register_patch",
11+
"install_hooks",
12+
"patch_instances_via_gc",
13+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Django instrumentation for Drift SDK."""
2+
3+
from .instrumentation import DjangoInstrumentation
4+
5+
__all__ = ["DjangoInstrumentation"]
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Django instrumentation for Drift SDK."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
from types import ModuleType
7+
from typing import Any, override, TYPE_CHECKING
8+
9+
logger = logging.getLogger(__name__)
10+
11+
if TYPE_CHECKING:
12+
from ...core.drift_sdk import TuskDrift
13+
14+
from ..base import InstrumentationBase
15+
from ..http import HttpTransformEngine
16+
17+
# Global flag to prevent duplicate middleware injection
18+
_middleware_injected = False
19+
20+
21+
class DjangoInstrumentation(InstrumentationBase):
22+
"""Django instrumentation via middleware injection.
23+
24+
Injects DriftMiddleware into Django's middleware stack at position 0
25+
(beginning of the stack) to capture all HTTP requests/responses.
26+
27+
Args:
28+
enabled: Whether instrumentation is enabled
29+
transforms: HTTP transform configuration
30+
"""
31+
32+
def __init__(self, enabled: bool = True, transforms: dict[str, Any] | None = None):
33+
self._transform_engine = HttpTransformEngine(
34+
self._resolve_http_transforms(transforms)
35+
)
36+
super().__init__(
37+
name="DjangoInstrumentation",
38+
module_name="django",
39+
supported_versions=">=3.2.0",
40+
enabled=enabled,
41+
)
42+
43+
def _resolve_http_transforms(
44+
self, provided: dict[str, Any] | list[dict[str, Any]] | None
45+
) -> list[dict[str, Any]] | None:
46+
"""Resolve HTTP transforms from provided config or SDK config."""
47+
if isinstance(provided, list):
48+
return provided
49+
if isinstance(provided, dict) and isinstance(provided.get("http"), list):
50+
return provided["http"]
51+
52+
from ...core.drift_sdk import TuskDrift
53+
sdk = TuskDrift.get_instance()
54+
transforms = getattr(sdk.config, "transforms", None)
55+
if isinstance(transforms, dict) and isinstance(transforms.get("http"), list):
56+
return transforms["http"]
57+
return None
58+
59+
@override
60+
def patch(self, module: ModuleType) -> None:
61+
"""Patch Django by injecting middleware into settings.
62+
63+
Args:
64+
module: The Django module
65+
"""
66+
global _middleware_injected
67+
68+
if _middleware_injected:
69+
logger.debug("[DjangoInstrumentation] Middleware already injected, skipping")
70+
return
71+
72+
try:
73+
# Import Django settings
74+
from django.conf import settings
75+
76+
# Check if settings are configured
77+
if not settings.configured:
78+
logger.warning(
79+
"[DjangoInstrumentation] Django settings not configured, cannot inject middleware"
80+
)
81+
return
82+
83+
# Detect middleware setting name (MIDDLEWARE vs MIDDLEWARE_CLASSES)
84+
middleware_setting = self._get_middleware_setting(settings)
85+
if not middleware_setting:
86+
logger.warning(
87+
"[DjangoInstrumentation] Could not find middleware setting, cannot inject"
88+
)
89+
return
90+
91+
# Get current middleware list
92+
current_middleware = list(getattr(settings, middleware_setting, []))
93+
94+
# Check if our middleware is already present
95+
middleware_path = "drift.instrumentation.django.middleware.DriftMiddleware"
96+
if middleware_path in current_middleware:
97+
logger.debug(
98+
"[DjangoInstrumentation] DriftMiddleware already in settings, skipping injection"
99+
)
100+
_middleware_injected = True
101+
return
102+
103+
# Insert DriftMiddleware at position 0 (beginning of stack)
104+
# This ensures we capture all requests, including those rejected by later middleware
105+
current_middleware.insert(0, middleware_path)
106+
107+
# Update Django settings
108+
setattr(settings, middleware_setting, current_middleware)
109+
110+
# Set transform engine on middleware class
111+
from .middleware import DriftMiddleware
112+
113+
DriftMiddleware.transform_engine = self._transform_engine # type: ignore
114+
115+
_middleware_injected = True
116+
logger.info(
117+
f"[DjangoInstrumentation] Injected DriftMiddleware at position 0 in {middleware_setting}"
118+
)
119+
print("Django instrumentation applied")
120+
121+
except ImportError as e:
122+
logger.warning(
123+
f"[DjangoInstrumentation] Could not import Django settings: {e}"
124+
)
125+
except Exception as e:
126+
logger.error(
127+
f"[DjangoInstrumentation] Failed to inject middleware: {e}",
128+
exc_info=True,
129+
)
130+
131+
def _get_middleware_setting(self, settings: Any) -> str | None:
132+
"""Detect which middleware setting name to use.
133+
134+
Django 1.10+ uses MIDDLEWARE, older versions use MIDDLEWARE_CLASSES.
135+
136+
Args:
137+
settings: Django settings object
138+
139+
Returns:
140+
The middleware setting name, or None if not found
141+
"""
142+
if hasattr(settings, "MIDDLEWARE"):
143+
return "MIDDLEWARE"
144+
elif hasattr(settings, "MIDDLEWARE_CLASSES"):
145+
return "MIDDLEWARE_CLASSES"
146+
return None

0 commit comments

Comments
 (0)