18
18
19
19
Usage
20
20
-----
21
+ Explicitly instrumenting a single client session:
21
22
22
- .. code:: python
23
+ .. code:: python
23
24
24
- import aiohttp
25
- from opentelemetry.instrumentation.aiohttp_client import (
26
- create_trace_config,
27
- url_path_span_name
28
- )
29
- import yarl
25
+ import aiohttp
26
+ from opentelemetry.instrumentation.aiohttp_client import (
27
+ create_trace_config,
28
+ url_path_span_name
29
+ )
30
+ import yarl
30
31
31
- def strip_query_params(url: yarl.URL) -> str:
32
- return str(url.with_query(None))
32
+ def strip_query_params(url: yarl.URL) -> str:
33
+ return str(url.with_query(None))
33
34
34
- async with aiohttp.ClientSession(trace_configs=[create_trace_config(
35
- # Remove all query params from the URL attribute on the span.
36
- url_filter=strip_query_params,
37
- # Use the URL's path as the span name.
38
- span_name=url_path_span_name
39
- )]) as session:
40
- async with session.get(url) as response:
41
- await response.text()
35
+ async with aiohttp.ClientSession(trace_configs=[create_trace_config(
36
+ # Remove all query params from the URL attribute on the span.
37
+ url_filter=strip_query_params,
38
+ # Use the URL's path as the span name.
39
+ span_name=url_path_span_name
40
+ )]) as session:
41
+ async with session.get(url) as response:
42
+ await response.text()
43
+
44
+ Instrumenting all client sessions:
45
+
46
+ .. code:: python
47
+
48
+ import aiohttp
49
+ from opentelemetry.instrumentation.aiohttp_client import (
50
+ AioHttpClientInstrumentor
51
+ )
42
52
53
+ # Enable instrumentation
54
+ AioHttpClientInstrumentor().instrument()
55
+
56
+ # Create a session and make an HTTP get request
57
+ async with aiohttp.ClientSession() as session:
58
+ async with session.get(url) as response:
59
+ await response.text()
60
+
61
+ API
62
+ ---
43
63
"""
44
64
45
- import contextlib
46
65
import socket
47
66
import types
48
67
import typing
49
68
50
69
import aiohttp
70
+ import wrapt
51
71
52
72
from opentelemetry import context as context_api
53
73
from opentelemetry import propagators , trace
54
74
from opentelemetry .instrumentation .aiohttp_client .version import __version__
55
- from opentelemetry .instrumentation .utils import http_status_to_canonical_code
56
- from opentelemetry .trace import SpanKind
75
+ from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
76
+ from opentelemetry .instrumentation .utils import (
77
+ http_status_to_canonical_code ,
78
+ unwrap ,
79
+ )
80
+ from opentelemetry .trace import SpanKind , TracerProvider , get_tracer
57
81
from opentelemetry .trace .status import Status , StatusCanonicalCode
58
82
83
+ _UrlFilterT = typing .Optional [typing .Callable [[str ], str ]]
84
+ _SpanNameT = typing .Optional [
85
+ typing .Union [typing .Callable [[aiohttp .TraceRequestStartParams ], str ], str ]
86
+ ]
87
+
59
88
60
89
def url_path_span_name (params : aiohttp .TraceRequestStartParams ) -> str :
61
90
"""Extract a span name from the request URL path.
@@ -73,12 +102,9 @@ def url_path_span_name(params: aiohttp.TraceRequestStartParams) -> str:
73
102
74
103
75
104
def create_trace_config (
76
- url_filter : typing .Optional [typing .Callable [[str ], str ]] = None ,
77
- span_name : typing .Optional [
78
- typing .Union [
79
- typing .Callable [[aiohttp .TraceRequestStartParams ], str ], str
80
- ]
81
- ] = None ,
105
+ url_filter : _UrlFilterT = None ,
106
+ span_name : _SpanNameT = None ,
107
+ tracer_provider : TracerProvider = None ,
82
108
) -> aiohttp .TraceConfig :
83
109
"""Create an aiohttp-compatible trace configuration.
84
110
@@ -104,6 +130,7 @@ def create_trace_config(
104
130
such as API keys or user personal information.
105
131
106
132
:param str span_name: Override the default span name.
133
+ :param tracer_provider: optional TracerProvider from which to get a Tracer
107
134
108
135
:return: An object suitable for use with :py:class:`aiohttp.ClientSession`.
109
136
:rtype: :py:class:`aiohttp.TraceConfig`
@@ -113,7 +140,7 @@ def create_trace_config(
113
140
# Explicitly specify the type for the `span_name` param and rtype to work
114
141
# around this issue.
115
142
116
- tracer = trace . get_tracer_provider (). get_tracer (__name__ , __version__ )
143
+ tracer = get_tracer (__name__ , __version__ , tracer_provider )
117
144
118
145
def _end_trace (trace_config_ctx : types .SimpleNamespace ):
119
146
context_api .detach (trace_config_ctx .token )
@@ -124,6 +151,10 @@ async def on_request_start(
124
151
trace_config_ctx : types .SimpleNamespace ,
125
152
params : aiohttp .TraceRequestStartParams ,
126
153
):
154
+ if context_api .get_value ("suppress_instrumentation" ):
155
+ trace_config_ctx .span = None
156
+ return
157
+
127
158
http_method = params .method .upper ()
128
159
if trace_config_ctx .span_name is None :
129
160
request_span_name = "HTTP {}" .format (http_method )
@@ -158,6 +189,9 @@ async def on_request_end(
158
189
trace_config_ctx : types .SimpleNamespace ,
159
190
params : aiohttp .TraceRequestEndParams ,
160
191
):
192
+ if trace_config_ctx .span is None :
193
+ return
194
+
161
195
if trace_config_ctx .span .is_recording ():
162
196
trace_config_ctx .span .set_status (
163
197
Status (
@@ -177,6 +211,9 @@ async def on_request_exception(
177
211
trace_config_ctx : types .SimpleNamespace ,
178
212
params : aiohttp .TraceRequestExceptionParams ,
179
213
):
214
+ if trace_config_ctx .span is None :
215
+ return
216
+
180
217
if trace_config_ctx .span .is_recording ():
181
218
if isinstance (
182
219
params .exception ,
@@ -193,6 +230,7 @@ async def on_request_exception(
193
230
status = StatusCanonicalCode .UNAVAILABLE
194
231
195
232
trace_config_ctx .span .set_status (Status (status ))
233
+ trace_config_ctx .span .record_exception (params .exception )
196
234
_end_trace (trace_config_ctx )
197
235
198
236
def _trace_config_ctx_factory (** kwargs ):
@@ -210,3 +248,84 @@ def _trace_config_ctx_factory(**kwargs):
210
248
trace_config .on_request_exception .append (on_request_exception )
211
249
212
250
return trace_config
251
+
252
+
253
+ def _instrument (
254
+ tracer_provider : TracerProvider = None ,
255
+ url_filter : _UrlFilterT = None ,
256
+ span_name : _SpanNameT = None ,
257
+ ):
258
+ """Enables tracing of all ClientSessions
259
+
260
+ When a ClientSession gets created a TraceConfig is automatically added to
261
+ the session's trace_configs.
262
+ """
263
+ # pylint:disable=unused-argument
264
+ def instrumented_init (wrapped , instance , args , kwargs ):
265
+ if context_api .get_value ("suppress_instrumentation" ):
266
+ return wrapped (* args , ** kwargs )
267
+
268
+ trace_configs = list (kwargs .get ("trace_configs" ) or ())
269
+
270
+ trace_config = create_trace_config (
271
+ url_filter = url_filter ,
272
+ span_name = span_name ,
273
+ tracer_provider = tracer_provider ,
274
+ )
275
+ trace_config .opentelemetry_aiohttp_instrumented = True
276
+ trace_configs .append (trace_config )
277
+
278
+ kwargs ["trace_configs" ] = trace_configs
279
+ return wrapped (* args , ** kwargs )
280
+
281
+ wrapt .wrap_function_wrapper (
282
+ aiohttp .ClientSession , "__init__" , instrumented_init
283
+ )
284
+
285
+
286
+ def _uninstrument ():
287
+ """Disables instrumenting for all newly created ClientSessions"""
288
+ unwrap (aiohttp .ClientSession , "__init__" )
289
+
290
+
291
+ def _uninstrument_session (client_session : aiohttp .ClientSession ):
292
+ """Disables instrumentation for the given ClientSession"""
293
+ # pylint: disable=protected-access
294
+ trace_configs = client_session ._trace_configs
295
+ client_session ._trace_configs = [
296
+ trace_config
297
+ for trace_config in trace_configs
298
+ if not hasattr (trace_config , "opentelemetry_aiohttp_instrumented" )
299
+ ]
300
+
301
+
302
+ class AioHttpClientInstrumentor (BaseInstrumentor ):
303
+ """An instrumentor for aiohttp client sessions
304
+
305
+ See `BaseInstrumentor`
306
+ """
307
+
308
+ def _instrument (self , ** kwargs ):
309
+ """Instruments aiohttp ClientSession
310
+
311
+ Args:
312
+ **kwargs: Optional arguments
313
+ ``tracer_provider``: a TracerProvider, defaults to global
314
+ ``url_filter``: A callback to process the requested URL prior to adding
315
+ it as a span attribute. This can be useful to remove sensitive data
316
+ such as API keys or user personal information.
317
+ ``span_name``: Override the default span name.
318
+ """
319
+ _instrument (
320
+ tracer_provider = kwargs .get ("tracer_provider" ),
321
+ url_filter = kwargs .get ("url_filter" ),
322
+ span_name = kwargs .get ("span_name" ),
323
+ )
324
+
325
+ def _uninstrument (self , ** kwargs ):
326
+ _uninstrument ()
327
+
328
+ @staticmethod
329
+ def uninstrument_session (client_session : aiohttp .ClientSession ):
330
+ """Disables instrumentation for the given session"""
331
+ _uninstrument_session (client_session )
0 commit comments