12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+ import types
15
16
from logging import getLogger
16
17
from time import time
17
18
from typing import Callable
24
25
get_global_response_propagator ,
25
26
)
26
27
from opentelemetry .instrumentation .utils import extract_attributes_from_object
28
+ from opentelemetry .instrumentation .wsgi import add_response_attributes
27
29
from opentelemetry .instrumentation .wsgi import (
28
- add_response_attributes ,
29
- collect_request_attributes ,
30
- wsgi_getter ,
30
+ collect_request_attributes as wsgi_collect_request_attributes ,
31
31
)
32
+ from opentelemetry .instrumentation .wsgi import wsgi_getter
32
33
from opentelemetry .propagate import extract
33
34
from opentelemetry .semconv .trace import SpanAttributes
34
35
from opentelemetry .trace import Span , SpanKind , use_span
43
44
from django .urls import Resolver404 , resolve
44
45
45
46
DJANGO_2_0 = django_version >= (2 , 0 )
47
+ DJANGO_3_0 = django_version >= (3 , 0 )
46
48
47
49
if DJANGO_2_0 :
48
50
# Since Django 2.0, only `settings.MIDDLEWARE` is supported, so new-style
@@ -67,6 +69,26 @@ def __call__(self, request):
67
69
except ImportError :
68
70
MiddlewareMixin = object
69
71
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
+
70
92
71
93
_logger = getLogger (__name__ )
72
94
_attributes_by_preference = [
@@ -91,6 +113,10 @@ def __call__(self, request):
91
113
]
92
114
93
115
116
+ def _is_asgi_request (request : HttpRequest ) -> bool :
117
+ return ASGIRequest is not None and isinstance (request , ASGIRequest )
118
+
119
+
94
120
class _DjangoMiddleware (MiddlewareMixin ):
95
121
"""Django Middleware for OpenTelemetry"""
96
122
@@ -140,12 +166,25 @@ def process_request(self, request):
140
166
if self ._excluded_urls .url_disabled (request .build_absolute_uri ("?" )):
141
167
return
142
168
169
+ is_asgi_request = _is_asgi_request (request )
170
+ if not _is_asgi_supported and is_asgi_request :
171
+ return
172
+
143
173
# pylint:disable=W0212
144
174
request ._otel_start_time = time ()
145
175
146
176
request_meta = request .META
147
177
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 ))
149
188
150
189
span = self ._tracer .start_span (
151
190
self ._get_span_name (request ),
@@ -155,12 +194,25 @@ def process_request(self, request):
155
194
),
156
195
)
157
196
158
- attributes = collect_request_attributes (request_meta )
197
+ attributes = collect_request_attributes (carrier )
159
198
160
199
if span .is_recording ():
161
200
attributes = extract_attributes_from_object (
162
201
request , self ._traced_request_attrs , attributes
163
202
)
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
+
164
216
for key , value in attributes .items ():
165
217
span .set_attribute (key , value )
166
218
@@ -207,15 +259,22 @@ def process_response(self, request, response):
207
259
if self ._excluded_urls .url_disabled (request .build_absolute_uri ("?" )):
208
260
return response
209
261
262
+ is_asgi_request = _is_asgi_request (request )
263
+ if not _is_asgi_supported and is_asgi_request :
264
+ return response
265
+
210
266
activation = request .META .pop (self ._environ_activation_key , None )
211
267
span = request .META .pop (self ._environ_span_key , None )
212
268
213
269
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
+ )
219
278
220
279
propagator = get_global_response_propagator ()
221
280
if propagator :
@@ -238,7 +297,7 @@ def process_response(self, request, response):
238
297
activation .__exit__ (None , None , None )
239
298
240
299
if self ._environ_token in request .META .keys ():
241
- detach (request .environ .get (self ._environ_token ))
300
+ detach (request .META .get (self ._environ_token ))
242
301
request .META .pop (self ._environ_token )
243
302
244
303
return response
0 commit comments