DataDog / dd-trace-php

Datadog PHP Clients
https://docs.datadoghq.com/tracing/setup/php
Other
501 stars 155 forks source link

[Feature] Support Grpc-Php extension with distributed tracing #916

Open Bibob7 opened 4 years ago

Bibob7 commented 4 years ago

Is your feature request related to a problem? Please describe. We would like to have full support for this extension: https://github.com/grpc/grpc-php with distributed tracing.

Describe the solution you'd like When sending an receiving grpc requests with https://github.com/grpc/grpc-php, I would like to be able to see distributed traces.

Describe alternatives you've considered We already implemented a tracing integration on php site, which works fine apart from distributed tracing. (see: #915) However, for the future it would be fine, if there is full support for this extension.

nesl247 commented 4 years ago

For our use case, this would allow us to have spans for calls to GCP services as it makes use of GRPC.

CAFxX commented 4 years ago

Same here, most of our internal calls happen over gRPC, and currently we're blind on PHP apps.

labbati commented 4 years ago

We do not have enough bandwidth to work on this integration during this quarter. Things might change if anyone of the other big challenges we are currently working on (support for PHP-8, adding profiling, stability/performance) will leave us some extra time. I just added the label up-for-grabs, in case anyone from the community is interested.

khepin commented 3 years ago

Leaving this here for folks who might find this issue. I have a limited use case for tracing gRPC at the moment and only care about simple requests / unary calls. Was able to get things nicely into DD with the following snippet:

\dd_trace_method(BaseStub::class, '_simpleRequest', function ($spanData, $args, $returnValue, $exception = null) {
    $method = $args[0];
    $name = array_values(array_filter(explode('/', $method)))[0];
    // Not great to have to do this, but without that, there's just no info available during the `wait` call
    // so that's what it is for now
    $returnValue->__dd_trace_info = [
        'name' => $name,
        'resource' => $args[0],
    ];
    $spanData->name = $returnValue->__dd_trace_info['name'];
    $spanData->resource = 'init:'.$returnValue->__dd_trace_info['resource'];
    $spanData->service = 'grpc';

});

\dd_trace_method(UnaryCall::class, 'wait', function ($spanData, $args, $returnValue, $exception = null) {
    $spanData->name = $this->__dd_trace_info['name'] ?? '__grpc_name';
    $spanData->resource = 'wait:'.$this->__dd_trace_info['resource'] ?? '__grpc_resource';
    $spanData->service = 'grpc';
});

Screen Shot 2021-03-05 at 3 08 20 PM

Bibob7 commented 3 years ago

@khepin: Thanks for your snipped, but also in this case, you will have no distributed tracing. For that we need to be able to exchange span and trace id via grpc metadata.

Bibob7 commented 3 years ago

@labbati: Are there any plans to work on that in the current or next quarter? For us this is still a relevant topic.

labbati commented 3 years ago

Hello @Bibob7, at the moment this has not been scheduled for Q2 and I have not an ETA I can commit to.

diutsu commented 3 years ago

We have achieved distributed tracing with the following:

use DDTrace\GlobalTracer;
use DDTrace\Tag;
use Grpc\Interceptor;

class DatadogGrpcInterceptor extends Interceptor
{
    const DEFAULT_BAGGAGE_HEADER_PREFIX = 'ot-baggage-';
    const DEFAULT_TRACE_ID_HEADER = 'x-datadog-trace-id';
    const DEFAULT_PARENT_ID_HEADER = 'x-datadog-parent-id';
    const DEFAULT_SAMPLING_PRIORITY_HEADER = 'x-datadog-sampling-priority';
    const DEFAULT_ORIGIN_HEADER = 'x-datadog-origin';

    public function interceptUnaryUnary($method, $argument, $deserialize, array $metadata = [], array $options = [], $continuation) // phpcs:ignore
    {
        $scope = GlobalTracer::get()->startActiveSpan("interceptUnaryUnary");
        $scope->getSpan()->setTag(Tag::SERVICE_NAME, "grpc-outbound");
        $scope->getSpan()->setTag(Tag::RESOURCE_NAME, $method);

        $spanContext = $scope->getSpan()->getContext();
        if ($spanContext->getTraceId() !== null) {
            $metadata[self::DEFAULT_TRACE_ID_HEADER] = array($spanContext->getTraceId());
        }
        if ($spanContext->getSpanId() !== null) {
            $metadata[self::DEFAULT_PARENT_ID_HEADER] = array($spanContext->getSpanId());
        }

        foreach ($spanContext as $key => $value) {
            if ($value !== null) {
                $metadata[self::DEFAULT_BAGGAGE_HEADER_PREFIX . $key] = array($value);
            }
        }

        $prioritySampling = GlobalTracer::get()->getPrioritySampling();
        if ($prioritySampling !== null) {
            $metadata[self::DEFAULT_SAMPLING_PRIORITY_HEADER] = array(strval($prioritySampling));
        }
        if (!empty($spanContext->origin)) {
            $metadata[self::DEFAULT_ORIGIN_HEADER] = array($spanContext->origin);
        }

        try {
            return $continuation($method, $argument, $deserialize, $metadata, $options);
        } finally {
            $scope->close();
        }
    }
}

And in the Channel/Client factory do something like this:

 private function createChannel(string $host, array $args = []): \Grpc\Channel
    {
        $channel = new \Grpc\Channel($host, $args);
        return \Grpc\Interceptor::intercept($channel, $this->interceptors);
    }
alexkb commented 1 year ago

@diutsu your method worked fantastically, thank you!

One minor change was where we set the service tag: $scope->getSpan()->setTag(Tag::SERVICE_NAME, getenv('DD_SERVICE')); - this way we can use the interceptor in multiple services.

For anyone else unsure about how to use the diutsu's method, you pass the channel that you create to the constructor of your php grpc service class e.g. new FooServiceClient('foo-service:5000', ['credentials' => null], $channel);.