22
22
import typing
23
23
import urllib
24
24
from functools import wraps
25
+ from typing import Tuple
25
26
26
27
from asgiref .compatibility import guarantee_single_callable
27
28
28
29
from opentelemetry import context , propagators , trace
29
30
from opentelemetry .ext .asgi .version import __version__ # noqa
31
+ from opentelemetry .instrumentation .utils import http_status_to_canonical_code
30
32
from opentelemetry .trace .status import Status , StatusCanonicalCode
31
33
32
34
@@ -44,37 +46,6 @@ def get_header_from_scope(scope: dict, header_name: str) -> typing.List[str]:
44
46
]
45
47
46
48
47
- def http_status_to_canonical_code (code : int , allow_redirect : bool = True ):
48
- # pylint:disable=too-many-branches,too-many-return-statements
49
- if code < 100 :
50
- return StatusCanonicalCode .UNKNOWN
51
- if code <= 299 :
52
- return StatusCanonicalCode .OK
53
- if code <= 399 :
54
- if allow_redirect :
55
- return StatusCanonicalCode .OK
56
- return StatusCanonicalCode .DEADLINE_EXCEEDED
57
- if code <= 499 :
58
- if code == 401 : # HTTPStatus.UNAUTHORIZED:
59
- return StatusCanonicalCode .UNAUTHENTICATED
60
- if code == 403 : # HTTPStatus.FORBIDDEN:
61
- return StatusCanonicalCode .PERMISSION_DENIED
62
- if code == 404 : # HTTPStatus.NOT_FOUND:
63
- return StatusCanonicalCode .NOT_FOUND
64
- if code == 429 : # HTTPStatus.TOO_MANY_REQUESTS:
65
- return StatusCanonicalCode .RESOURCE_EXHAUSTED
66
- return StatusCanonicalCode .INVALID_ARGUMENT
67
- if code <= 599 :
68
- if code == 501 : # HTTPStatus.NOT_IMPLEMENTED:
69
- return StatusCanonicalCode .UNIMPLEMENTED
70
- if code == 503 : # HTTPStatus.SERVICE_UNAVAILABLE:
71
- return StatusCanonicalCode .UNAVAILABLE
72
- if code == 504 : # HTTPStatus.GATEWAY_TIMEOUT:
73
- return StatusCanonicalCode .DEADLINE_EXCEEDED
74
- return StatusCanonicalCode .INTERNAL
75
- return StatusCanonicalCode .UNKNOWN
76
-
77
-
78
49
def collect_request_attributes (scope ):
79
50
"""Collects HTTP request attributes from the ASGI scope and returns a
80
51
dictionary to be used as span creation attributes."""
@@ -134,11 +105,19 @@ def set_status_code(span, status_code):
134
105
span .set_status (Status (http_status_to_canonical_code (status_code )))
135
106
136
107
137
- def get_default_span_name (scope ):
138
- """Default implementation for name_callback"""
108
+ def get_default_span_details (scope : dict ) -> Tuple [str , dict ]:
109
+ """Default implementation for span_details_callback
110
+
111
+ Args:
112
+ scope: the asgi scope dictionary
113
+
114
+ Returns:
115
+ a tuple of the span, and any attributes to attach to the
116
+ span.
117
+ """
139
118
method_or_path = scope .get ("method" ) or scope .get ("path" )
140
119
141
- return method_or_path
120
+ return method_or_path , {}
142
121
143
122
144
123
class OpenTelemetryMiddleware :
@@ -149,15 +128,18 @@ class OpenTelemetryMiddleware:
149
128
150
129
Args:
151
130
app: The ASGI application callable to forward requests to.
152
- name_callback: Callback which calculates a generic span name for an
153
- incoming HTTP request based on the ASGI scope.
154
- Optional: Defaults to get_default_span_name.
131
+ span_details_callback: Callback which should return a string
132
+ and a tuple, representing the desired span name and a
133
+ dictionary with any additional span attributes to set.
134
+ Optional: Defaults to get_default_span_details.
155
135
"""
156
136
157
- def __init__ (self , app , name_callback = None ):
137
+ def __init__ (self , app , span_details_callback = None ):
158
138
self .app = guarantee_single_callable (app )
159
139
self .tracer = trace .get_tracer (__name__ , __version__ )
160
- self .name_callback = name_callback or get_default_span_name
140
+ self .span_details_callback = (
141
+ span_details_callback or get_default_span_details
142
+ )
161
143
162
144
async def __call__ (self , scope , receive , send ):
163
145
"""The ASGI application
@@ -173,13 +155,15 @@ async def __call__(self, scope, receive, send):
173
155
token = context .attach (
174
156
propagators .extract (get_header_from_scope , scope )
175
157
)
176
- span_name = self .name_callback (scope )
158
+ span_name , additional_attributes = self .span_details_callback (scope )
159
+ attributes = collect_request_attributes (scope )
160
+ attributes .update (additional_attributes )
177
161
178
162
try :
179
163
with self .tracer .start_as_current_span (
180
164
span_name + " asgi" ,
181
165
kind = trace .SpanKind .SERVER ,
182
- attributes = collect_request_attributes ( scope ) ,
166
+ attributes = attributes ,
183
167
):
184
168
185
169
@wraps (receive )
0 commit comments