Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Inject trace_configs as an argument to opentelemetry aiohttp_client._instrument instrumentation
**Is your feature request related to a problem?**

I would like to add data to a span based on on_request_chunk_sent event. Using the request/response hooks doesn't provide a good solution for my use case since it's "too late" in the timeline. 

**Describe the solution you'd like**

I would like to pass trace_configs (or a single trace_config) to aiohttp_client opentelemetry instrumentation. It will allow me to specify which events I would like to "catch" and will allow more customization solving many use-cases that the request/response hooks cannot.

**Describe alternatives you've considered**

I've doubled the instrument_init method and set it up with my own trace_configs, bypassing opentelemetry implementation. 

**Additional context**
If this issue will be approved I would like to contribute my solution, thanks



Discussion:
@nemoshlag Can you share little example of what this change/solution looks like and what are you trying to achieve?
Hi @srikanthccv sure, pseudo usage from client side:

`def trace_config(...)-> aiohttp.TraceConfig:
    async def on_request_chunk_sent(...):
        # do stuff

AioHttpClientInstrumentor._instument(tracer_provider, request_hook, response_hook, trace_configs=trace_config)`

And on opentelemetry instrumentation side:

`def _instrument(tracer_provider: TracerProvider = None, args, kwargs):
    def instrumented_init(wrapped, instance, args, kwargs) -> None:
        trace_configs = list(kwargs.get("trace_configs") or ())

        trace_config = create_trace_config(
            tracer_provider=tracer_provider,
            request_hook=kwargs.get("request_hook"),
            response_hook=kwargs.get("response_hook"),
        )

        trace_configs.append(trace_config)

        ...`

Feel free to send a PR.

Resolving patch:
diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py
@@ -262,18 +262,24 @@ def _instrument(
     url_filter: _UrlFilterT = None,
     request_hook: _RequestHookT = None,
     response_hook: _ResponseHookT = None,
+    trace_configs: typing.Optional[aiohttp.TraceConfig] = None,
 ):
     """Enables tracing of all ClientSessions
 
     When a ClientSession gets created a TraceConfig is automatically added to
     the session's trace_configs.
     """
+
+    if trace_configs is None:
+        trace_configs = []
+
     # pylint:disable=unused-argument
     def instrumented_init(wrapped, instance, args, kwargs):
         if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
             return wrapped(*args, **kwargs)
 
-        trace_configs = list(kwargs.get("trace_configs") or ())
+        if kwargs.get("trace_configs"):
+            trace_configs.extend(kwargs.get("trace_configs"))
 
         trace_config = create_trace_config(
             url_filter=url_filter,
@@ -328,12 +334,15 @@ def _instrument(self, **kwargs):
                     such as API keys or user personal information.
                 ``request_hook``: An optional callback that is invoked right after a span is created.
                 ``response_hook``: An optional callback which is invoked right before the span is finished processing a response.
+                ``trace_configs``: An optional list of aiohttp.TraceConfig items, allowing customize enrichment of spans
+                 based on aiohttp events (see specification: https://docs.aiohttp.org/en/stable/tracing_reference.html)
         """
         _instrument(
             tracer_provider=kwargs.get("tracer_provider"),
             url_filter=kwargs.get("url_filter"),
             request_hook=kwargs.get("request_hook"),
             response_hook=kwargs.get("response_hook"),
+            trace_configs=kwargs.get("trace_configs"),
         )
 
     def _uninstrument(self, **kwargs):


Test patch:
diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py
--- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py
+++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py
@@ -386,6 +386,20 @@ def test_instrument(self):
         )
         self.assertEqual(200, span.attributes[SpanAttributes.HTTP_STATUS_CODE])
 
+    def test_instrument_with_custom_trace_config(self):
+        AioHttpClientInstrumentor().uninstrument()
+        AioHttpClientInstrumentor().instrument(
+            trace_configs=[aiohttp_client.create_trace_config()]
+        )
+
+        self.assert_spans(0)
+
+        run_with_test_server(
+            self.get_default_request(), self.URL, self.default_handler
+        )
+
+        self.assert_spans(2)
+
     def test_instrument_with_existing_trace_config(self):
         trace_config = aiohttp.TraceConfig()
 
@@ -432,7 +446,7 @@ async def uninstrument_request(server: aiohttp.test_utils.TestServer):
         run_with_test_server(
             self.get_default_request(), self.URL, self.default_handler
         )
-        self.assert_spans(1)
+        self.assert_spans(2)
 
     def test_suppress_instrumentation(self):
         token = context.attach(