async-aws / aws

AWS SDK with readable code and async responses
https://async-aws.com
MIT License
433 stars 131 forks source link

X-Ray trace propagation #729

Open datashaman opened 4 years ago

datashaman commented 4 years ago

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 commented 4 years ago

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.

jderusse commented 4 years ago

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?

Nyholm commented 4 years ago

Friendly ping

GrahamCampbell commented 1 year ago

cc @datashaman