1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import types
1516from logging import getLogger
1617from time import time
1718from typing import Callable
2425 get_global_response_propagator ,
2526)
2627from opentelemetry .instrumentation .utils import extract_attributes_from_object
28+ from opentelemetry .instrumentation .wsgi import add_response_attributes
2729from opentelemetry .instrumentation .wsgi import (
28- add_response_attributes ,
29- collect_request_attributes ,
30- wsgi_getter ,
30+ collect_request_attributes as wsgi_collect_request_attributes ,
3131)
32+ from opentelemetry .instrumentation .wsgi import wsgi_getter
3233from opentelemetry .propagate import extract
3334from opentelemetry .semconv .trace import SpanAttributes
3435from opentelemetry .trace import Span , SpanKind , use_span
4344 from django .urls import Resolver404 , resolve
4445
4546DJANGO_2_0 = django_version >= (2 , 0 )
47+ DJANGO_3_0 = django_version >= (3 , 0 )
4648
4749if DJANGO_2_0 :
4850 # Since Django 2.0, only `settings.MIDDLEWARE` is supported, so new-style
@@ -67,6 +69,26 @@ def __call__(self, request):
6769 except ImportError :
6870 MiddlewareMixin = object
6971
72+ if DJANGO_3_0 :
73+ from django .core .handlers .asgi import ASGIRequest
74+ else :
75+ ASGIRequest = None
76+
77+ # try/except block exclusive for optional ASGI imports.
78+ try :
79+ from opentelemetry .instrumentation .asgi import asgi_getter
80+ from opentelemetry .instrumentation .asgi import (
81+ collect_request_attributes as asgi_collect_request_attributes ,
82+ )
83+ from opentelemetry .instrumentation .asgi import set_status_code
84+
85+ _is_asgi_supported = True
86+ except ImportError :
87+ asgi_getter = None
88+ asgi_collect_request_attributes = None
89+ set_status_code = None
90+ _is_asgi_supported = False
91+
7092
7193_logger = getLogger (__name__ )
7294_attributes_by_preference = [
@@ -91,6 +113,10 @@ def __call__(self, request):
91113]
92114
93115
116+ def _is_asgi_request (request : HttpRequest ) -> bool :
117+ return ASGIRequest is not None and isinstance (request , ASGIRequest )
118+
119+
94120class _DjangoMiddleware (MiddlewareMixin ):
95121 """Django Middleware for OpenTelemetry"""
96122
@@ -140,12 +166,25 @@ def process_request(self, request):
140166 if self ._excluded_urls .url_disabled (request .build_absolute_uri ("?" )):
141167 return
142168
169+ is_asgi_request = _is_asgi_request (request )
170+ if not _is_asgi_supported and is_asgi_request :
171+ return
172+
143173 # pylint:disable=W0212
144174 request ._otel_start_time = time ()
145175
146176 request_meta = request .META
147177
148- token = attach (extract (request_meta , getter = wsgi_getter ))
178+ if is_asgi_request :
179+ carrier = request .scope
180+ carrier_getter = asgi_getter
181+ collect_request_attributes = asgi_collect_request_attributes
182+ else :
183+ carrier = request_meta
184+ carrier_getter = wsgi_getter
185+ collect_request_attributes = wsgi_collect_request_attributes
186+
187+ token = attach (extract (request_meta , getter = carrier_getter ))
149188
150189 span = self ._tracer .start_span (
151190 self ._get_span_name (request ),
@@ -155,12 +194,25 @@ def process_request(self, request):
155194 ),
156195 )
157196
158- attributes = collect_request_attributes (request_meta )
197+ attributes = collect_request_attributes (carrier )
159198
160199 if span .is_recording ():
161200 attributes = extract_attributes_from_object (
162201 request , self ._traced_request_attrs , attributes
163202 )
203+ if is_asgi_request :
204+ # ASGI requests include extra attributes in request.scope.headers.
205+ attributes = extract_attributes_from_object (
206+ types .SimpleNamespace (
207+ ** {
208+ name .decode ("latin1" ): value .decode ("latin1" )
209+ for name , value in request .scope .get ("headers" , [])
210+ }
211+ ),
212+ self ._traced_request_attrs ,
213+ attributes ,
214+ )
215+
164216 for key , value in attributes .items ():
165217 span .set_attribute (key , value )
166218
@@ -207,15 +259,22 @@ def process_response(self, request, response):
207259 if self ._excluded_urls .url_disabled (request .build_absolute_uri ("?" )):
208260 return response
209261
262+ is_asgi_request = _is_asgi_request (request )
263+ if not _is_asgi_supported and is_asgi_request :
264+ return response
265+
210266 activation = request .META .pop (self ._environ_activation_key , None )
211267 span = request .META .pop (self ._environ_span_key , None )
212268
213269 if activation and span :
214- add_response_attributes (
215- span ,
216- f"{ response .status_code } { response .reason_phrase } " ,
217- response ,
218- )
270+ if is_asgi_request :
271+ set_status_code (span , response .status_code )
272+ else :
273+ add_response_attributes (
274+ span ,
275+ f"{ response .status_code } { response .reason_phrase } " ,
276+ response ,
277+ )
219278
220279 propagator = get_global_response_propagator ()
221280 if propagator :
@@ -238,7 +297,7 @@ def process_response(self, request, response):
238297 activation .__exit__ (None , None , None )
239298
240299 if self ._environ_token in request .META .keys ():
241- detach (request .environ .get (self ._environ_token ))
300+ detach (request .META .get (self ._environ_token ))
242301 request .META .pop (self ._environ_token )
243302
244303 return response
0 commit comments