Transactions in other transaction
Hello ! I ran into a problem when creating my own transaction. I have the following logic: a request to the endpoint, then some database manipulation, run celery task: http requests (AsyncClient, httpx) and database manipulation. In my case, I want httpx requests to become transactions, not spans. In my code, i do something like this:
from httpx import AsyncClient
import elasticapm
from elasticapm import get_client, traces
class apm_trace(object):
def __init__(
self,
transaction_type: str,
transaction_name: str,
context: dict | None = None,
trace_parent=None,
trace_parent_propagate: bool = False,
):
self.transaction = None
self.parent_transaction = None
self.transaction_type = transaction_type
self.transaction_name = transaction_name
self.context = context
self.trace_parent = trace_parent
self.trace_parent_propagate = trace_parent_propagate
self.client = get_client()
def __enter__(self):
self.parent_transaction = traces.execution_context.get_transaction()
self.parent_span = traces.execution_context.get_span()
traces.execution_context.set_span(None)
if self.parent_transaction and self.trace_parent_propagate:
self.trace_parent = self.parent_transaction.trace_parent
self.transaction = self.client.begin_transaction(
self.transaction_type, trace_parent=self.trace_parent
)
elasticapm.set_transaction_name(self.transaction_name, override=False)
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
self.client.error_logger.error("Exception during timing of func", exc_info=True)
self.client.end_transaction(name=self.transaction_name)
if self.parent_transaction:
traces.execution_context.set_transaction(self.parent_transaction)
if self.parent_span:
traces.execution_context.set_span(self.parent_span)
return False
class SomeClient(AsyncClient):
transaction_type = "some_type"
transaction_name = "My transaction"
async def make_request(*args, **kwargs):
with apm_trace(transaction_type, transaction_name, trace_parent_propagate=True) as tracer:
try:
response = await self.request(*args, **kwargs)
except (HTTPError, ConnectTimeout, NetworkError, TimeoutException) as e:
elasticapm.set_transaction_result(type(e))
raise
finally:
context = ...
# some manipulating with context
elasticapm.set_custom_context(context)
return response
# suppose there is some magic with db
# somewere in celery task:
client = SomeClient()
response = await client.make_request(*args, **kwargs) # my request
# suppose there is some magic with db

The screenshot shows trace. According to the correct logic, my http requests should go before the spans, which are indicated in the red square. But they come after the spans. What am I doing wrong?
Environment:
- OS: Win and Linux
- Python version: 3.10.4
- Framework and version: fastapi: 0.75.0
- APM Server version: _
- Agent version: 6.9.1
- HTTPX: "^0.22.0"
I don't see particular red flags, except of course the atypical use of our execution_context. ;)
I'm not sure your code example is complete enough to compare to your trace screenshot. I'm seeing something like three http requests in your screenshot and am not sure what the red box is supposed to be from your code example. Can you clarify?
It would also be interesting to know what you want to achieve exactly by interpreting the http requests as transactions.
That being said, I think the trace view actually shows exactly what you tell it to show. The horizontal axis shows a temporal relation (if A is left of B, A happened before B), while the vertical tree structure shows causal relations (if B is nested under A, B is caused by A). From how I understand it, the spans in the red box happen after the three http requests (so they are right of them), but are not strictly causally related, so they are not nested under them.
Hey @reydnn I'm going to close this for now. Please keep us posted if there's more we can discuss.