patrickkerrigan / php-xray

A PHP instrumentation library for AWS X-Ray
BSD 3-Clause "New" or "Revised" License
63 stars 26 forks source link

Correlate front-end and back-end web tiers? #11

Closed icelava closed 3 years ago

icelava commented 3 years ago

I'm experimenting with getting a PHP front-end web tier to pass down the trace ID when making web requests to a similarly-instrumented back-end ASP.NET API tier in order to correlate the requests.

Raw cURL hack

<?php include 'vendor\autoload.php';?>
<?php

$xrayHeader = "X-Amzn-Trace-Id";

\Pkerrigan\Xray\Trace::getInstance()
    ->setTraceHeader($_SERVER[$xrayHeader] ?? null)
    ->setName('XRayFrontPHP')
    ->setUrl($_SERVER['REQUEST_URI'])
    ->setMethod($_SERVER['REQUEST_METHOD'])
    ->begin(10);

// Use cURL to pass on X-Ray trace ID.
$apiUrl = "http://localhost:8081/api/tier";
$curlyBraces = array("{", "}");
$traceId = \Pkerrigan\Xray\Trace::getInstance()->getTraceId();
$reqHeaders[] = $xrayHeader . ": Root=" . $traceId .";Sampled=1";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER , $reqHeaders);
curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
$delayTime = curl_exec($ch);
curl_close($ch);

\Pkerrigan\Xray\Trace::getInstance()
    ->end()
    ->setResponseCode(http_response_code())
    ->setError(http_response_code() >= 400 && http_response_code() < 500)
    ->setFault(http_response_code() >= 500)
    ->submit(new \Pkerrigan\Xray\Submission\DaemonSegmentSubmitter());

?>

The X-Ray console trace UI actually does show the relation, but I can't figure out why the service map does not visualise their relationship? Any ideas what extra actions must be done?

AWX X-Ray related trace but no service map linkage

icelava commented 3 years ago

Ok looks like the 'trick' behind it is the PHP layer must add its own sub-segment for the back-end HTTP request.

<?php include 'vendor\autoload.php';?>
<?php

$xrayHeader = "X-Amzn-Trace-Id";

\Pkerrigan\Xray\Trace::getInstance()
    ->setTraceHeader($_SERVER[$xrayHeader] ?? null)
    ->setName('XRayFrontPHP')
    ->setUrl($_SERVER['REQUEST_URI'])
    ->setMethod($_SERVER['REQUEST_METHOD'])
    ->begin(100);

     // Artificial delay between main segment and sub-segment.
     usleep(10000);

// Add separate sub-segment for back-end API tier.
\Pkerrigan\Xray\Trace::getInstance()
    ->getCurrentSegment()
    ->addSubsegment(
        (new \Pkerrigan\Xray\RemoteSegment())->begin(100)
    );

// Use cURL to pass on X-Ray trace ID.
$apiUrl = "http://localhost:8081/api/tier";
$curlyBraces = array("{", "}");
$traceId = \Pkerrigan\Xray\Trace::getInstance()->getTraceId();
$reqHeaders[] = $xrayHeader . ": Root=" . $traceId .";Sampled=1";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER , $reqHeaders);
curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
$delayTime = curl_exec($ch);
curl_close($ch);

\Pkerrigan\Xray\Trace::getInstance()
    ->getCurrentSegment()
    ->setName('XRayBackApi')
    ->end();

\Pkerrigan\Xray\Trace::getInstance()
    ->end()
    ->setResponseCode(http_response_code())
    ->setError(http_response_code() >= 400 && http_response_code() < 500)
    ->setFault(http_response_code() >= 500)
    ->submit(new \Pkerrigan\Xray\Submission\DaemonSegmentSubmitter());

?>

X-Ray then appears to be able to "jot the dots" between the two service nodes. AWX X-Ray related trace correlate service map with subsegment

patrickkerrigan commented 3 years ago

It looks like you're pretty much there with this already, but there's a couple of extra things you can do to help make sure X-Ray always links the traces of your two services properly:

The first is to use a HttpSegment instead of a RemoteSegment when wrapping the call to your backend service. This allows you to record the URL, request method, and fault/error status of the backend call from the frontend service's perspecitve, which makes the way the trace is shown in the X-Ray console a little richer. More importantly it also allows you to mark the segment as being a call to a traced service by calling the setTraced method on it. This signals to X-Ray that it should wait for the backend service to submit a trace before it processes the current one, which should mean you can drop the artificial delay you're adding.

The seond thing you can probably add is the ID of the wrapping segment in the header that you pass to the backend service. This lets X-Ray know exactly where to link the two traces together and should help make sure the API call shows up in the correct place in the trace view of the console.

Using your latest code snippet, the above would look something like this:

<?php include 'vendor\autoload.php';?>
<?php

$xrayHeader = "X-Amzn-Trace-Id";

\Pkerrigan\Xray\Trace::getInstance()
    ->setTraceHeader($_SERVER[$xrayHeader] ?? null)
    ->setName('XRayFrontPHP')
    ->setUrl($_SERVER['REQUEST_URI'])
    ->setMethod($_SERVER['REQUEST_METHOD'])
    ->begin(100);

$apiUrl = "http://localhost:8081/api/tier";

// Add separate sub-segment for back-end API tier.
$subSegment = (new \Pkerrigan\Xray\HttpSegment())
    ->setTraced(true)
    ->setName('XRayBackApi')
    ->setUrl($apiUrl)
    ->setMethod('GET');

\Pkerrigan\Xray\Trace::getInstance()
    ->getCurrentSegment()
    ->addSubsegment(
        $subSegment->begin(100)
    );

// Use cURL to pass on X-Ray trace ID.
$curlyBraces = array("{", "}");
$traceId = \Pkerrigan\Xray\Trace::getInstance()->getTraceId();
$reqHeaders[] = $xrayHeader . ": Root=" . $traceId .";Sampled=1;Parent=" . \Pkerrigan\Xray\Trace::getInstance()->getCurrentSegment()->getId();

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER , $reqHeaders);
curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
$delayTime = curl_exec($ch);
curl_close($ch);

// End HTTP subsegment for API call
\Pkerrigan\Xray\Trace::getInstance()
    ->getCurrentSegment()
    ->end()
    ->setResponseCode(...) // Use the response code from your cURL request to fill in these 3 bits of data
    ->setError(...)
    ->setFault(...);

// End trace
\Pkerrigan\Xray\Trace::getInstance()
    ->end()
    ->setResponseCode(http_response_code())
    ->setError(http_response_code() >= 400 && http_response_code() < 500)
    ->setFault(http_response_code() >= 500)
    ->submit(new \Pkerrigan\Xray\Submission\DaemonSegmentSubmitter());

?>

Hopefully this helps!

icelava commented 2 years ago

Noted. Thank you!