39
39
from azure .core .rest import HttpResponse , HttpRequest
40
40
from azure .core .settings import settings
41
41
from azure .core .tracing import SpanKind
42
+ from ...tracing ._tracer import default_tracer_provider , TracerProvider
43
+ from ...tracing ._models import TracingOptions
42
44
43
45
if TYPE_CHECKING :
44
- from azure . core .tracing ._abstract_span import (
46
+ from .. .tracing ._abstract_span import (
45
47
AbstractSpan ,
46
48
)
49
+ from ...tracing .opentelemetry_span import OpenTelemetrySpan
47
50
48
51
HTTPResponseType = TypeVar ("HTTPResponseType" , HttpResponse , LegacyHttpResponse )
49
52
HTTPRequestType = TypeVar ("HTTPRequestType" , HttpRequest , LegacyHttpRequest )
@@ -61,10 +64,7 @@ def _default_network_span_namer(http_request: HTTPRequestType) -> str:
61
64
:returns: The string to use as network span name
62
65
:rtype: str
63
66
"""
64
- path = urllib .parse .urlparse (http_request .url ).path
65
- if not path :
66
- path = "/"
67
- return path
67
+ return http_request .method
68
68
69
69
70
70
class DistributedTracingPolicy (SansIOHTTPPolicy [HTTPRequestType , HTTPResponseType ]):
@@ -77,35 +77,91 @@ class DistributedTracingPolicy(SansIOHTTPPolicy[HTTPRequestType, HTTPResponseTyp
77
77
"""
78
78
79
79
TRACING_CONTEXT = "TRACING_CONTEXT"
80
+
81
+ # Current HTTP semantic conventions
82
+ _HTTP_RESEND_COUNT = "http.request.resend_count"
83
+ _USER_AGENT_ORIGINAL = "user_agent.original"
84
+ _HTTP_REQUEST_METHOD = "http.request.method"
85
+ _URL_FULL = "url.full"
86
+ _HTTP_RESPONSE_STATUS_CODE = "http.response.status_code"
87
+ _SERVER_ADDRESS = "server.address"
88
+ _SERVER_PORT = "server.port"
89
+ _ERROR_TYPE = "error.type"
90
+
91
+ # Legacy HTTP semantic conventions
92
+ _HTTP_USER_AGENT = "http.user_agent"
93
+ _HTTP_METHOD = "http.method"
94
+ _HTTP_URL = "http.url"
95
+ _HTTP_STATUS_CODE = "http.status_code"
96
+ _NET_PEER_NAME = "net.peer.name"
97
+ _NET_PEER_PORT = "net.peer.port"
98
+
99
+ # Azure attributes
80
100
_REQUEST_ID = "x-ms-client-request-id"
81
101
_RESPONSE_ID = "x-ms-request-id"
82
- _HTTP_RESEND_COUNT = "http.request.resend_count"
102
+ _REQUEST_ID_ATTR = "az.client_request_id"
103
+ _RESPONSE_ID_ATTR = "az.service_request_id"
83
104
84
- def __init__ (self , ** kwargs : Any ):
105
+ def __init__ (self , * , tracer_provider : Optional [ TracerProvider ] = None , * *kwargs : Any ):
85
106
self ._network_span_namer = kwargs .get ("network_span_namer" , _default_network_span_namer )
86
107
self ._tracing_attributes = kwargs .get ("tracing_attributes" , {})
108
+ self ._tracer_provider = tracer_provider
87
109
88
110
def on_request (self , request : PipelineRequest [HTTPRequestType ]) -> None :
89
111
ctxt = request .context .options
90
112
try :
91
- span_impl_type = settings .tracing_implementation ()
92
- if span_impl_type is None :
113
+ tracing_options : TracingOptions = ctxt .pop ("tracing_options" , {})
114
+ tracing_enabled = settings .tracing_enabled ()
115
+
116
+ # User can explicitly disable tracing for this request.
117
+ user_enabled = tracing_options .get ("enabled" )
118
+ if user_enabled is False :
93
119
return
94
120
121
+ # If tracing is disabled globally and user didn't explicitly enable it, don't trace.
122
+ if not tracing_enabled and user_enabled is None :
123
+ return
124
+
125
+ span_impl_type = settings .tracing_implementation ()
95
126
namer = ctxt .pop ("network_span_namer" , self ._network_span_namer )
96
127
tracing_attributes = ctxt .pop ("tracing_attributes" , self ._tracing_attributes )
97
128
span_name = namer (request .http_request )
98
129
99
- span = span_impl_type (name = span_name , kind = SpanKind .CLIENT )
100
- for attr , value in tracing_attributes .items ():
101
- span .add_attribute (attr , value )
102
- span .start ()
130
+ span_attributes = {** tracing_attributes , ** tracing_options .get ("attributes" , {})}
131
+
132
+ if span_impl_type :
133
+ # If the plugin is enabled, prioritize it over the core tracing.
134
+ span = span_impl_type (name = span_name , kind = SpanKind .CLIENT )
135
+ for attr , value in span_attributes .items ():
136
+ span .add_attribute (attr , value ) # type: ignore
103
137
104
- headers = span .to_header ()
105
- request .http_request .headers .update (headers )
138
+ headers = span .to_header ()
139
+ request .http_request .headers .update (headers )
140
+
141
+ request .context [self .TRACING_CONTEXT ] = span
142
+ else :
143
+ # Otherwise, use the core tracing.
144
+ tracer = (
145
+ self ._tracer_provider .get_tracer ()
146
+ if self ._tracer_provider
147
+ else default_tracer_provider .get_tracer ()
148
+ )
149
+ if not tracer :
150
+ return
151
+
152
+ core_span = tracer .start_span (
153
+ name = span_name ,
154
+ kind = SpanKind .CLIENT ,
155
+ attributes = span_attributes ,
156
+ record_exception = tracing_options .get ("record_exception" , True ),
157
+ )
158
+
159
+ trace_context_headers = tracer .get_trace_context ()
160
+ request .http_request .headers .update (trace_context_headers )
161
+ request .context [self .TRACING_CONTEXT ] = core_span
106
162
107
- request .context [self .TRACING_CONTEXT ] = span
108
163
except Exception as err : # pylint: disable=broad-except
164
+ print (err )
109
165
_LOGGER .warning ("Unable to start network span: %s" , err )
110
166
111
167
def end_span (
@@ -126,21 +182,26 @@ def end_span(
126
182
if self .TRACING_CONTEXT not in request .context :
127
183
return
128
184
129
- span : "AbstractSpan" = request .context [self .TRACING_CONTEXT ]
185
+ span = request .context [self .TRACING_CONTEXT ]
130
186
http_request : Union [HttpRequest , LegacyHttpRequest ] = request .http_request
131
187
if span is not None :
132
- span .set_http_attributes (http_request , response = response )
188
+ self ._set_http_attributes (span , request = http_request , response = response )
189
+ set_attribute_func = (
190
+ getattr (span , "add_attribute" ) if hasattr (span , "add_attribute" ) else getattr (span , "set_attribute" )
191
+ )
192
+ end_func = getattr (span , "finish" , None ) or getattr (span , "end" )
193
+
133
194
if request .context .get ("retry_count" ):
134
- span . add_attribute (self ._HTTP_RESEND_COUNT , request .context ["retry_count" ])
195
+ set_attribute_func (self ._HTTP_RESEND_COUNT , request .context ["retry_count" ])
135
196
request_id = http_request .headers .get (self ._REQUEST_ID )
136
197
if request_id is not None :
137
- span . add_attribute (self ._REQUEST_ID , request_id )
198
+ set_attribute_func (self ._REQUEST_ID_ATTR , request_id )
138
199
if response and self ._RESPONSE_ID in response .headers :
139
- span . add_attribute (self ._RESPONSE_ID , response .headers [self ._RESPONSE_ID ])
200
+ set_attribute_func (self ._RESPONSE_ID_ATTR , response .headers [self ._RESPONSE_ID ])
140
201
if exc_info :
141
202
span .__exit__ (* exc_info )
142
203
else :
143
- span . finish ()
204
+ end_func ()
144
205
145
206
def on_response (
146
207
self ,
@@ -151,3 +212,43 @@ def on_response(
151
212
152
213
def on_exception (self , request : PipelineRequest [HTTPRequestType ]) -> None :
153
214
self .end_span (request , exc_info = sys .exc_info ())
215
+
216
+ def _set_http_attributes (
217
+ self ,
218
+ span : Union ["AbstractSpan" , "OpenTelemetrySpan" ],
219
+ request : Union [HttpRequest , LegacyHttpRequest ],
220
+ response : Optional [HTTPResponseType ] = None ,
221
+ ) -> None :
222
+ """
223
+ Add correct attributes for a http client span.
224
+
225
+ :param span: The span to add attributes to.
226
+ :type span: ~azure.core.tracing.AbstractSpan or ~azure.core.tracing.opentelemetry_span.OpenTelemetrySpan
227
+ :param request: The request made
228
+ :type request: azure.core.rest.HttpRequest
229
+ :param response: The response received from the server. Is None if no response received.
230
+ :type response: ~azure.core.pipeline.transport.HttpResponse or ~azure.core.pipeline.transport.AsyncHttpResponse
231
+ """
232
+ set_attribute_func = (
233
+ getattr (span , "add_attribute" ) if hasattr (span , "add_attribute" ) else getattr (span , "set_attribute" )
234
+ )
235
+
236
+ set_attribute_func (self ._HTTP_REQUEST_METHOD , request .method )
237
+ set_attribute_func (self ._URL_FULL , request .url )
238
+
239
+ parsed_url = urllib .parse .urlparse (request .url )
240
+ if parsed_url .hostname :
241
+ set_attribute_func (self ._SERVER_ADDRESS , parsed_url .hostname )
242
+ if parsed_url .port and parsed_url .port not in [80 , 443 ]:
243
+ set_attribute_func (self ._SERVER_PORT , parsed_url .port )
244
+
245
+ user_agent = request .headers .get ("User-Agent" )
246
+ if user_agent :
247
+ set_attribute_func (self ._USER_AGENT_ORIGINAL , user_agent )
248
+ if response and response .status_code :
249
+ set_attribute_func (self ._HTTP_RESPONSE_STATUS_CODE , response .status_code )
250
+ if response .status_code >= 400 :
251
+ set_attribute_func (self ._ERROR_TYPE , str (response .status_code ))
252
+ else :
253
+ set_attribute_func (self ._HTTP_RESPONSE_STATUS_CODE , 504 )
254
+ set_attribute_func (self ._ERROR_TYPE , "504" )
0 commit comments