[PHP, React] Distributed Tracing

It is not very clear from the documentation how to combine several transactions under a single TraceID.

I use the standard BrowserTracing integration from the documentation.

Problem 1:
Documentation on Javascript SDK says:

If your frontend is making requests to a different domain, you will need to add it there to propagate the sentry-trace header to the backend services, which is required to link transactions together as part of a single trace.

This works perfectly by sending the sentry-trace header in queries for the domain api.example.com.
Backend and frontend transactions of the main page are linked via the meta tag.
However, XHR for the current domain do not include this header. How do I combine main page and XHR transactions in this case?

Problem 2:
How do I bind XHR transactions to the initial TraceID? Will it be enough to use SpanContext::fromTraceparent when initializing the context of the first Span?
$spanCtx = \Sentry\Tracing\SpanContext::fromTraceparent($_SERVER['HTTP_SENTRY_TRACE']??'');

up maybe?


So we have a section in the Laravel docs that explains this: https://docs.sentry.io/platforms/php/guides/laravel/performance/#connect-your-frontend

It depends on where you start your trace. If it’s on the server and you render a template you want to do something similar like in the link I posted.

Otherwise yes, what you wrote is what you need, but instead of creating a Span create a Transaction with SentrySdk::getCurrentHub()->startTransaction(TransactionContext::fromSentryTrace($header)) and call startChild on it.

Does this help?

Unfortunately, it did not help much.
I will try to explain it step by step.

  1. Client makes a request to the server. The server returns HTML with meta sentry-trace in the header. (this works correctly - backend and frontend traces have the same trace_id).
    The Sentry initialization in the backend is now in middleware, which means it actually starts tracing for each request.
    The backend checks if the sentry-trace header has arrived, and if it has, it creates a Span from Trace Parent (based on the value from the header), and if not, it creates a new one (in order to output it into the meta of the page when the HTML is rendered)

     $transactionCtx = new \Sentry\Tracing\TransactionContext();
     $transaction = \Sentry\startTransaction($transactionCtx);
     $spanCtx = \Sentry\Tracing\SpanContext::fromTraceparent($_SERVER['HTTP_SENTRY_TRACE']??'');
     $span = $transaction->startChild($spanCtx);
     // temporary hack for meta tag rendering
     define('SENTRY_TRACE_ID', $span->toTraceparent());
  2. On the frontend Sentry is initialized like this:

         dsn: 'https://sentry.dsn',
         integrations: [new TracingIntegrations.BrowserTracing({
             tracingOrigins: ['localhost', 'domain.org', 'api.domain.org'],
         tracesSampleRate: 1.0,
  3. As the React application renders the page, it makes several requests to two APIs, the old one is on the same domain as the site (e.g., domain.org), the new one is on a subdomain (e.g., api.domain.org)
    I see that until the document.onload event occurs, Sentry adds the sentry-trace header to requests to the new API, but it doesn’t add it to ajax requests to the current domain (which is not the main problem).

But despite the fact that I explicitly pass sentry-trace to API requests and initialize Span with this identifier, these traces end up with a separate trace_id and are in no way related to frontend trace.

What I want to see: frontend trace, backend trace, and all ajax requests are combined under one trace parent id.
What I actually see: frontend, backend traces are merged under one trace parent id, all ajax requests have a separate trace_id without a parent and are in no way related to the original request.

As a result, I see in Page(JS API) tracing that ajax request to API took a long time, but I can’t associate it with a specific trace in any way (at least you can’t find it simply by trace parent id)

For me, it will fiddling with tracingOrigins - which wasn’t working quite as we would have expected. This was defining whether the sentry-trace header should be sent with the XHR or not.

A second problem was that //<domains/path/abc is a valid url. it means “visit the url on the same protocol as you are currently loaded from”. This was a pattern that is common during http -> https url migrations and may still be around in code.

The default tracingOptions matches against the regex /^// or “starts with a forward slash”, which the example does. CORS may block xhr requests when sending the sentry-trace header to external APIs (for example google analytics).

A piece of learning we had (and thanks to this ticket) was that tracingOrigins is what should be used instead of the deprecated allowUrls.

1 Like

A second issue we’re noticing is that once the “?sentry_key=xyz” call is made, tracing stops.

For us, the sentry envelop call is happening before all xhr has completed.

This can be changed with the idleTimeout tracingOptions parameter. For our larger SPAs, setting the idleTimeout to something like 10000 allowed all xhr to be captured.

Finally I’ve found working solution.


// Start the whole Transaction from a trace, not a single Span
$transactionCtx = \Sentry\Tracing\TransactionContext::fromSentryTrace($_SERVER['HTTP_SENTRY_TRACE']??'');
$transaction = \Sentry\startTransaction($transactionCtx);

$spanCtx = new \Sentry\Tracing\SpanContext();
// This is deprecated (and not working) \Sentry\Tracing\SpanContext::fromTraceparent($_SERVER['HTTP_SENTRY_TRACE']??'');
$span = $transaction->startChild($spanCtx);

define('SENTRY_TRACE_ID', $span->toTraceparent());

Frontend: I’ve just forgotten about “/^//”

tracingOrigins: ['localhost', 'api.domain.org', /^\//],

1 Like

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.