aws icon indicating copy to clipboard operation
aws copied to clipboard

X-Ray trace propagation

Open datashaman opened this issue 5 years ago • 4 comments

Hi there,

Thanks for a great package! I'm still feeling my way around the way that AWS X-Ray tracing works, so apologies if this is a naive request.

AWS Lambda sends a trace ID via header Lambda-Runtime-Trace-Id which needs to be put into the environment as _X_AMZN_TRACE_ID when developing a runtime, which I'm busy developing for a PHP application.

I think for X-Ray to map the services used as a client from within the Lambda, the trace ID needs to be relayed through as a header to the AWS service being used.

Is there a way to do this using the API, or should I use an independent tracer like https://github.com/patrickkerrigan/php-xray ? I don't see an extension point where headers can be added.

Thanks.

PS: I am by no means sure any of the above would work so I will override one method the long way around described below and report back.

datashaman avatar Aug 11 '20 04:08 datashaman

I guess I could subclass each client I'm using and rewrite each method to use a modified request.

For example:

public function getItem($input): GetItemOutput
{
    $input = GetItemInput::create($input);
    $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'GetItem', 'region' => $input->getRegion()]));

    return new GetItemOutput($response);
}

becomes:

public function getItem($input): GetItemOutput
{
    $input = GetItemInput::create($input);
    $request = $input->request();
    
    if ($traceId = getenv('_X_AMZN_TRACE_ID')) {
        $request = $request->withHeader('Lambda-Runtime-Trace-Id', $traceId);
    }
    
    $response = $this->getResponse($request, new RequestContext(['operation' => 'GetItem', 'region' => $input->getRegion()]));

    return new GetItemOutput($response);
}

It feels inelegant though. Also still unsure if Lambda-Runtime-Trace-Id is correct. That's the name of the header passed into the Lambda at the start of the invocation.

datashaman avatar Aug 11 '20 05:08 datashaman

I think you can decorate the HttpClientInterface injected in each client. In that way you can override the headers like:

class XRayClient implements HttpClientInterface
{
    private $client;

    public function __construct(HttpClientInterface $client)
    {
        $this->client = $client;
    }

    public function request(string $method, string $url, array $options = []): ResponseInterface
    {
        if (!isset($options['headers']['Lambda-Runtime-Trace-Id']) && $traceId = getenv(['_X_AMZN_TRACE_ID')) {
            $options['headers']['Lambda-Runtime-Trace-Id'] = $traceId;
        }

        return $this->client->request($method, $url, $options);
    }

    public function stream($responses, float $timeout = null): ResponseStreamInterface
    {
        return $this->client->stream($responses, $timeout);
    }
}

$dynamoDb = new DynamoDbClient([], null, new XRayClient(HttpClient::create()));

I wonder if this shouldn't be part of the core package @Nyholm : propagating the X-RAY env variable to all servives called.

@datashaman could you confirm that my snippet works?

jderusse avatar Aug 11 '20 17:08 jderusse

Friendly ping

Nyholm avatar Sep 13 '20 10:09 Nyholm

cc @datashaman

GrahamCampbell avatar Nov 13 '22 18:11 GrahamCampbell