[Feature] Adopt opentracing interface and support jaeger

This PR unties all Zipkin details from the hyper/tracer. All tracing interaction is done through the standard Opentracing interfaces. Configuration format is slightly modified according to the discussion in #453. Two adapters are provided out of box, Zipkin and Jaeger. The architecture is made pluggable now. Anything that implements opentracing can be used as the underlying tracer. Users are free to implement their own solution.

Note you need to "composer require jonahgeorge/jaeger-client-php" to use Jaeger as tracer.
This commit is contained in:
Reasno 2019-08-29 16:29:37 +08:00
parent 47e53de968
commit fcad7ce6bf
21 changed files with 598 additions and 198 deletions

View File

@ -20,12 +20,12 @@
"google/protobuf": "^3.6.1",
"grpc/grpc": "^1.15",
"guzzlehttp/guzzle": "^6.3",
"jcchavezs/zipkin-opentracing": "^0.1.2",
"jean85/pretty-package-versions": "^1.2",
"monolog/monolog": "^1.24",
"nesbot/carbon": "^2.0",
"nikic/fast-route": "^1.3",
"nikic/php-parser": "^4.1",
"openzipkin/zipkin": "^1.3.2",
"php-amqplib/php-amqplib": "^2.7",
"php-di/phpdoc-reader": "^2.0.1",
"psr/container": "^1.0",
@ -42,6 +42,7 @@
"require-dev": {
"doctrine/common": "@stable",
"friendsofphp/php-cs-fixer": "^2.14",
"jonahgeorge/jaeger-client-php": "^0.4.4",
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"php-di/php-di": "^6.0",

View File

@ -38,6 +38,7 @@
<directory suffix="Test.php">./src/snowflake/tests</directory>
<directory suffix="Test.php">./src/task/tests</directory>
<directory suffix="Test.php">./src/testing/tests</directory>
<directory suffix="Test.php">./src/tracer/tests</directory>
<directory suffix="Test.php">./src/utils/tests</directory>
<directory suffix="Test.php">./src/websocket-client/tests</directory>
</testsuite>

View File

@ -17,17 +17,20 @@
"hyperf/di": "~1.1.0",
"hyperf/guzzle": "~1.1.0",
"hyperf/utils": "~1.1.0",
"openzipkin/zipkin": "^1.3.2"
"jcchavezs/zipkin-opentracing":"^0.1.2",
"opentracing/opentracing":"1.0.0-beta5"
},
"require-dev": {
"hyperf/event": "~1.1.0",
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.0.0",
"friendsofphp/php-cs-fixer": "^2.9"
"friendsofphp/php-cs-fixer": "^2.9",
"jonahgeorge/jaeger-client-php": "^0.4.4"
},
"suggest": {
"hyperf/event": "Required to use DbQueryExecutedListener."
"hyperf/event": "Required to use DbQueryExecutedListener.",
"jonahgeorge/jaeger-client-php":"Required to use jaeger tracing."
},
"autoload": {
"psr-4": {

View File

@ -13,12 +13,16 @@ declare(strict_types=1);
use Zipkin\Samplers\BinarySampler;
return [
'default' => env('TRACER_DRIVER', 'zipkin'),
'enable' => [
'guzzle' => env('TRACER_ENABLE_GUZZLE', false),
'redis' => env('TRACER_ENABLE_REDIS', false),
'db' => env('TRACER_ENABLE_DB', false),
'method' => env('TRACER_ENABLE_METHOD', false),
],
'tracer' => [
'zipkin' => [
'driver' => Hyperf\Tracer\Adapter\ZipkinTracerFactory::class,
'app' => [
'name' => env('APP_NAME', 'skeleton'),
// Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null
@ -32,4 +36,25 @@ return [
],
'sampler' => BinarySampler::createAsAlwaysSample(),
],
'jaeger' => [
'driver' => Hyperf\Tracer\Adapter\JaegerTracerFactory::class,
'name' => env('APP_NAME', 'skeleton'),
'options' => [
/*
* You can uncomment the sampler lines to use custom strategy.
*
* For more available configurations,
* @see https://github.com/jonahgeorge/jaeger-client-php
*/
// 'sampler' => [
// 'type' => \Jaeger\SAMPLER_TYPE_CONST,
// 'param' => true,
// ],,
'local_agent' => [
'reporting_host' => env('JAEGER_REPORTING_HOST', 'localhost'),
'reporting_port' => env('JAEGER_REPORTING_PORT', 5775),
],
],
],
],
];

View File

@ -10,7 +10,7 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer;
namespace Hyperf\Tracer\Adapter;
use Hyperf\Guzzle\ClientFactory as GuzzleClientFactory;
use RuntimeException;

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer\Adapter;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Tracer\Contract\NamedFactoryInterface;
use Jaeger\Config;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use const Jaeger\SAMPLER_TYPE_CONST;
class JaegerTracerFactory implements NamedFactoryInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var ConfigInterface
*/
private $config;
/**
* @var string
*/
private $prefix;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->config = $container->get(ConfigInterface::class);
}
public function make(string $name)
{
$this->prefix = "opentracing.tracer.{$name}.";
[$name, $options] = $this->parseConfig();
$logger = null;
if ($this->container->has(LoggerInterface::class)) {
$logger = $this->container->get(LoggerInterface::class);
}
$cache = null;
if ($this->container->has(CacheItemPoolInterface::class)) {
$cache = $this->container->get(CacheItemPoolInterface::class);
}
$jaegerConfig = new Config(
$options,
$name,
$logger,
$cache
);
return $jaegerConfig->initializeTracer();
}
private function parseConfig(): array
{
return [
$this->getConfig('name', 'skeleton'),
$this->getConfig('options', [
'sampler' => [
'type' => SAMPLER_TYPE_CONST,
'param' => true,
],
'logging' => false,
]),
];
}
private function getConfig(string $key, $default)
{
return $this->config->get($this->prefix . $key, $default);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer\Adapter;
use Hyperf\Contract\ConfigInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class JaegerTracerFactory
{
public function __invoke(ContainerInterface $container)
{
$config = $container->get(ConfigInterface::class);
[$name, $options] = $this->parseConfig();
$logger = $container->get(LoggerInterface::class);
$cache = $container->get(CacheItemPoolInterface::class);
$jaegerConfig = new Jaeger\Config(
$options,
$name,
$logger,
$cache,
);
return $jaegerConfig->initializeTracer();
}
private function parseConfig(): array
{
return [
$this->getConfig('name', 'skeleton'),
$this->getConfig('options', [
'sampler' => [
'type' => Jaeger\SAMPLER_TYPE_CONST,
'param' => true,
],
'logging' => true,
]),
];
}
private function getConfig(string $key, $default)
{
$prefix = 'opentracing.jaeger.';
return $this->config->get($prefix . $key, $default);
}
}

View File

@ -10,32 +10,54 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer;
namespace Hyperf\Tracer\Adapter;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Tracer\Contract\NamedFactoryInterface;
use Psr\Container\ContainerInterface;
use Zipkin\Endpoint;
use Zipkin\Reporters\Http;
use Zipkin\Samplers\BinarySampler;
use Zipkin\TracingBuilder;
use ZipkinOpenTracing\Tracer;
class TracingBuilderFactory
class ZipkinTracerFactory implements NamedFactoryInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var ConfigInterface
*/
private $config;
public function __invoke(ContainerInterface $container): TracingBuilder
/**
* @var string
*/
private $prefix = 'opentracing.zipkin.';
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->config = $container->get(ConfigInterface::class);
}
public function make(string $name)
{
if (! empty($name)) {
$this->prefix = "opentracing.tracer.{$name}.";
}
[$app, $options, $sampler] = $this->parseConfig();
$endpoint = Endpoint::create($app['name'], $app['ipv4'], $app['ipv6'], $app['port']);
$reporter = new Http($container->get(HttpClientFactory::class), $options);
return TracingBuilder::create()
$reporter = new Http($this->container->get(HttpClientFactory::class), $options);
$tracing = TracingBuilder::create()
->havingLocalEndpoint($endpoint)
->havingSampler($sampler)
->havingReporter($reporter);
->havingReporter($reporter)
->build();
return new Tracer($tracing);
}
private function parseConfig(): array
@ -57,7 +79,6 @@ class TracingBuilderFactory
private function getConfig(string $key, $default)
{
$prefix = 'opentracing.zipkin.';
return $this->config->get($prefix . $key, $default);
return $this->config->get($this->prefix . $key, $default);
}
}

View File

@ -16,16 +16,20 @@ use GuzzleHttp\Client;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SwitchManager;
use Hyperf\Tracer\Tracing;
use Hyperf\Utils\Context;
use OpenTracing\Tracer;
use Psr\Http\Message\ResponseInterface;
use Zipkin\Propagation\Map;
use const OpenTracing\Formats\TEXT_MAP;
/**
* @Aspect
*/
class HttpClientAspect implements AroundInterface
{
use SpanStarter;
public $classes = [
Client::class . '::requestAsync',
];
@ -33,18 +37,18 @@ class HttpClientAspect implements AroundInterface
public $annotations = [];
/**
* @var Tracing
* @var Tracer
*/
private $tracing;
private $tracer;
/**
* @var SwitchManager
*/
private $switchManager;
public function __construct(Tracing $tracing, SwitchManager $switchManager)
public function __construct(Tracer $tracer, SwitchManager $switchManager)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
$this->switchManager = $switchManager;
}
@ -64,18 +68,20 @@ class HttpClientAspect implements AroundInterface
$method = $arguments['keys']['method'] ?? 'Null';
$uri = $arguments['keys']['uri'] ?? 'Null';
$key = "HTTP Request [{$method}] {$uri}";
$span = $this->tracing->span($key);
$span->tag('source', $proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName);
$span = $this->startSpan($key);
$span->setTag('source', $proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName);
$appendHeaders = [];
// Injects the context into the wire
$injector = $this->tracing->getPropagation()->getInjector(new Map());
$injector($span->getContext(), $appendHeaders);
$this->tracer->inject(
$span->getContext(),
TEXT_MAP,
$appendHeaders,
);
$options['headers'] = array_replace($options['headers'] ?? [], $appendHeaders);
$proceedingJoinPoint->arguments['keys']['options'] = $options;
$span->start();
$result = $proceedingJoinPoint->process();
if ($result instanceof ResponseInterface) {
$span->tag('status', $result->getStatusCode());
$span->setTag('status', $result->getStatusCode());
}
$span->finish();
return $result;

View File

@ -15,14 +15,17 @@ namespace Hyperf\Tracer\Aspect;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SwitchManager;
use Hyperf\Tracer\Tracing;
use OpenTracing\Tracer;
/**
* Aspect.
* @Aspect.
*/
class MethodAspect extends AbstractAspect
{
use SpanStarter;
/**
* @var array
*/
@ -31,18 +34,18 @@ class MethodAspect extends AbstractAspect
];
/**
* @var Tracing
* @var Tracer
*/
private $tracing;
private $tracer;
/**
* @var SwitchManager
*/
private $switchManager;
public function __construct(Tracing $tracing, SwitchManager $switchManager)
public function __construct(Tracer $tracer, SwitchManager $switchManager)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
$this->switchManager = $switchManager;
}
@ -56,8 +59,7 @@ class MethodAspect extends AbstractAspect
}
$key = $proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName;
$span = $this->tracing->span($key);
$span->start();
$span = $this->startSpan($key);
$result = $proceedingJoinPoint->process();
$span->finish();
return $result;

View File

@ -16,14 +16,17 @@ use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Redis\Redis;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SwitchManager;
use Hyperf\Tracer\Tracing;
use OpenTracing\Tracer;
/**
* @Aspect
*/
class RedisAspect implements AroundInterface
{
use SpanStarter;
/**
* @var array
*/
@ -40,16 +43,16 @@ class RedisAspect implements AroundInterface
/**
* @var Tracing
*/
private $tracing;
private $tracer;
/**
* @var SwitchManager
*/
private $switchManager;
public function __construct(Tracing $tracing, SwitchManager $switchManager)
public function __construct(Tracer $tracer, SwitchManager $switchManager)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
$this->switchManager = $switchManager;
}
@ -63,11 +66,10 @@ class RedisAspect implements AroundInterface
}
$arguments = $proceedingJoinPoint->arguments['keys'];
$span = $this->tracing->span('Redis' . '::' . $arguments['name']);
$span->start();
$span->tag('arguments', json_encode($arguments['arguments']));
$span = $this->startSpan('Redis' . '::' . $arguments['name']);
$span->setTag('arguments', json_encode($arguments['arguments']));
$result = $proceedingJoinPoint->process();
$span->tag('result', json_encode($result));
$span->setTag('result', json_encode($result));
$span->finish();
return $result;
}

View File

@ -16,13 +16,16 @@ use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Tracer\Annotation\Trace;
use Hyperf\Tracer\Tracing;
use Hyperf\Tracer\SpanStarter;
use OpenTracing\Tracer;
/**
* @Aspect
*/
class TraceAnnotationAspect implements AroundInterface
{
use SpanStarter;
public $classes = [];
public $annotations = [
@ -30,13 +33,13 @@ class TraceAnnotationAspect implements AroundInterface
];
/**
* @var Tracing
* @var Tracer
*/
private $tracing;
private $tracer;
public function __construct(Tracing $tracing)
public function __construct(Tracer $tracer)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
}
/**
@ -52,9 +55,8 @@ class TraceAnnotationAspect implements AroundInterface
} else {
$name = $source;
}
$span = $this->tracing->span($name);
$span->tag('source', $source);
$span->start();
$span = $this->startSpan($name);
$span->setTag('source', $source);
$result = $proceedingJoinPoint->process();
$span->finish();
return $result;

View File

@ -13,8 +13,7 @@ declare(strict_types=1);
namespace Hyperf\Tracer;
use GuzzleHttp\Client;
use Zipkin\Tracing;
use Zipkin\TracingBuilder;
use OpenTracing\Tracer;
class ConfigProvider
{
@ -22,8 +21,7 @@ class ConfigProvider
{
return [
'dependencies' => [
Tracing::class => \Hyperf\Tracer\Tracing::class,
TracingBuilder::class => TracingBuilderFactory::class,
Tracer::class => TracerFactory::class,
SwitchManager::class => SwitchManagerFactory::class,
Client::class => Client::class,
],

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer\Contract;
interface NamedFactoryInterface
{
/**
* Create the object from factory.
*/
public function make(string $name);
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer\Exception;
class InvalidArgumentException extends \InvalidArgumentException
{
}

View File

@ -15,29 +15,32 @@ namespace Hyperf\Tracer\Listener;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SwitchManager;
use Hyperf\Tracer\Tracing;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Str;
use OpenTracing\Tracer;
/**
* @Listener
*/
class DbQueryExecutedListener implements ListenerInterface
{
use SpanStarter;
/**
* @var Tracing
* @var Tracer
*/
private $tracing;
private $tracer;
/**
* @var SwitchManager
*/
private $switchManager;
public function __construct(Tracing $tracing, SwitchManager $switchManager)
public function __construct(Tracer $tracer, SwitchManager $switchManager)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
$this->switchManager = $switchManager;
}
@ -64,10 +67,11 @@ class DbQueryExecutedListener implements ListenerInterface
}
$endTime = microtime(true);
$span = $this->tracing->span('db.query');
$span->start((int) (($endTime - $event->time / 1000) * 1000 * 1000));
$span->tag('db.sql', $sql);
$span->tag('db.query_time', $event->time . ' ms');
$span = $this->startSpan('db.query', [
'start_time' => (int) (($endTime - $event->time / 1000) * 1000 * 1000),
]);
$span->setTag('db.sql', $sql);
$span->setTag('db.query_time', $event->time . ' ms');
$span->finish((int) ($endTime * 1000 * 1000));
}
}

View File

@ -12,24 +12,26 @@ declare(strict_types=1);
namespace Hyperf\Tracer\Middleware;
use Hyperf\Tracer\Tracing;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Utils\Coroutine;
use OpenTracing\Tracer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use const Zipkin\Kind\SERVER;
class TraceMiddeware implements MiddlewareInterface
{
/**
* @var Tracing
*/
private $tracing;
use SpanStarter;
public function __construct(Tracing $tracing)
/**
* @var Tracer
*/
private $tracer;
public function __construct(Tracer $tracer)
{
$this->tracing = $tracing;
$this->tracer = $tracer;
}
/**
@ -41,15 +43,11 @@ class TraceMiddeware implements MiddlewareInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$span = $this->buildSpan($request);
$span->start();
$response = $handler->handle($request);
$span->finish();
$tracer = $this->tracing->getTracer();
defer(function () use ($tracer) {
$tracer->flush();
defer(function () {
$this->tracer->flush();
});
return $response;
@ -58,14 +56,13 @@ class TraceMiddeware implements MiddlewareInterface
protected function buildSpan(ServerRequestInterface $request)
{
$uri = $request->getUri();
$span = $this->tracing->span('request', SERVER);
$span->tag('coroutine.id', (string) Coroutine::id());
$span->tag('request.path', (string) $uri);
$span->tag('request.method', $request->getMethod());
$span = $this->startSpan('request');
$span->setTag('coroutine.id', (string) Coroutine::id());
$span->setTag('request.path', (string) $uri);
$span->setTag('request.method', $request->getMethod());
foreach ($request->getHeaders() as $key => $value) {
$span->tag('request.header.' . $key, implode(', ', $value));
$span->setTag('request.header.' . $key, implode(', ', $value));
}
return $span;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer;
use Hyperf\Utils\Context;
use OpenTracing\Span;
use Psr\Http\Message\ServerRequestInterface;
use const OpenTracing\Formats\TEXT_MAP;
use const OpenTracing\Tags\SPAN_KIND;
use const OpenTracing\Tags\SPAN_KIND_RPC_SERVER;
trait SpanStarter
{
/**
* Helper method to start a span while setting context.
* @param string $name
* @param string $kind
* @param array $option
*/
protected function startSpan(
string $name,
array $option = [],
string $kind = SPAN_KIND_RPC_SERVER
): Span {
$root = Context::get('tracer.root');
if (! $root instanceof Span) {
/** @var ServerRequestInterface $request */
$request = Context::get(ServerRequestInterface::class);
if (! $request instanceof ServerRequestInterface) {
throw new \RuntimeException('ServerRequest object missing.');
}
$carrier = array_map(function ($header) {
return $header[0];
}, $request->getHeaders());
// Extracts the context from the HTTP headers.
$spanContext = $this->tracer->extract(TEXT_MAP, $carrier);
if ($spanContext) {
$option['child_of'] = $spanContext;
}
$root = $this->tracer->startSpan($name, $option);
$root->setTag(SPAN_KIND, $kind);
Context::set('tracer.root', $root);
return $root;
}
$option['child_of'] = $root->getContext();
$child = $this->tracer->startSpan($name, $option);
$child->setTag(SPAN_KIND, $kind);
return $child;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Tracer\Adapter\ZipkinTracerFactory;
use Hyperf\Tracer\Contract\NamedFactoryInterface;
use Hyperf\Tracer\Exception\InvalidArgumentException;
use Psr\Container\ContainerInterface;
class TracerFactory
{
/**
* @var ConfigInterface
*/
private $config;
public function __invoke(ContainerInterface $container)
{
$this->config = $container->get(ConfigInterface::class);
$name = $this->config->get('opentracing.default');
// v1.0 has no default config. Fallback to v1.0 mode for backward compatibility.
if (empty($name)) {
$factory = new ZipkinTracerFactory($container);
return $factory->make('');
}
$driver = $this->config->get("opentracing.tracer.{$name}.driver");
if (empty($driver)) {
throw new InvalidArgumentException(
sprintf('The tracing config [%s] doesn\'t contain a valid driver.', $name)
);
}
$factory = new $driver($container);
if (! ($factory instanceof NamedFactoryInterface)) {
throw new InvalidArgumentException(
sprintf('The driver %s is not a valid factory.', $driver)
);
}
return $factory->make($name);
}
}

View File

@ -1,104 +0,0 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Tracer;
use Hyperf\Utils\Context;
use Hyperf\Utils\Traits\CoroutineProxy;
use Psr\Http\Message\ServerRequestInterface;
use Zipkin\Propagation\Map;
use Zipkin\Propagation\Propagation;
use Zipkin\Span;
use Zipkin\Tracer;
use Zipkin\TracingBuilder;
use const Zipkin\Kind\SERVER;
class Tracing implements \Zipkin\Tracing
{
use CoroutineProxy;
protected $proxyKey = \Zipkin\Tracing::class;
/**
* @var TracingBuilder
*/
protected $tracingBuilder;
public function __construct(TracingBuilder $tracingBuilder)
{
$this->tracingBuilder = $tracingBuilder;
}
public function span(string $name, string $kind = SERVER)
{
$root = Context::get('tracer.root');
if (! $root instanceof Span) {
/** @var ServerRequestInterface $request */
$request = Context::get(ServerRequestInterface::class);
if (! $request instanceof ServerRequestInterface) {
throw new \RuntimeException('ServerRequest object missing.');
}
$carrier = array_map(function ($header) {
return $header[0];
}, $request->getHeaders());
// Extracts the context from the HTTP headers.
$extractor = $this->getPropagation()->getExtractor(new Map());
$extractedContext = $extractor($carrier);
$root = $this->getTracer()->nextSpan($extractedContext);
$root->setName($name);
$root->setKind($kind);
Context::set('tracer.root', $root);
return $root;
}
$child = $this->getTracer()->newChild($root->getContext());
$child->setName($name);
$child->setKind($kind);
return $child;
}
/**
* All tracing commands start with a {@link Span}. Use a tracer to create spans.
*/
public function getTracer(): Tracer
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* When a trace leaves the process, it needs to be propagated, usually via headers. This utility
* is used to inject or extract a trace context from remote requests.
*/
public function getPropagation(): Propagation
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* When true, no recording is done and nothing is reported to zipkin. However, trace context is
* still injected into outgoing requests.
*
* @see Span#isNoop()
*/
public function isNoop(): bool
{
return $this->__call(__FUNCTION__, func_get_args());
}
protected function getTargetObject(): \Zipkin\Tracing
{
$tracing = Context::get($this->proxyKey);
if (! $tracing instanceof \Zipkin\Tracing) {
Context::set($this->proxyKey, $tracing = $this->tracingBuilder->build());
}
return $tracing;
}
}

View File

@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Tracer;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Container;
use Hyperf\Tracer\TracerFactory;
use Hyperf\Utils\ApplicationContext;
use Mockery;
use PHPUnit\Framework\TestCase;
use Zipkin\Samplers\BinarySampler;
/**
* @internal
* @coversNothing
*/
class TracerFactoryTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
}
public function testOldSetting()
{
$config = new Config([
'opentracing' => [
'zipkin' => [
'app' => [
'name' => env('APP_NAME', 'skeleton'),
// Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null
'ipv4' => '127.0.0.1',
'ipv6' => null,
'port' => 9501,
],
'options' => [
'endpoint_url' => env('ZIPKIN_ENDPOINT_URL', 'http://localhost:9411/api/v2/spans'),
'timeout' => env('ZIPKIN_TIMEOUT', 1),
],
'sampler' => BinarySampler::createAsAlwaysSample(),
],
],
]);
$container = $this->getContainer($config);
$factory = new TracerFactory();
$this->assertInstanceOf(\ZipkinOpenTracing\Tracer::class, $factory($container));
}
public function testZipkinFactory()
{
$config = new Config([
'opentracing' => [
'default' => 'zipkin',
'enable' => [
],
'tracer' => [
'zipkin' => [
'driver' => \Hyperf\Tracer\Adapter\ZipkinTracerFactory::class,
'app' => [
'name' => 'skeleton',
// Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null
'ipv4' => '127.0.0.1',
'ipv6' => null,
'port' => 9501,
],
'options' => [
],
'sampler' => BinarySampler::createAsAlwaysSample(),
],
'jaeger' => [
'driver' => \Hyperf\Tracer\Adapter\JaegerTracerFactory::class,
'name' => 'skeleton',
'options' => [
],
],
],
],
]);
$container = $this->getContainer($config);
$factory = new TracerFactory();
$this->assertInstanceOf(\ZipkinOpenTracing\Tracer::class, $factory($container));
}
public function testJaegerFactory()
{
$config = new Config([
'opentracing' => [
'default' => 'jaeger',
'enable' => [
],
'tracer' => [
'zipkin' => [
'driver' => \Hyperf\Tracer\Adapter\ZipkinTracerFactory::class,
'app' => [
'name' => 'skeleton',
// Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null
'ipv4' => '127.0.0.1',
'ipv6' => null,
'port' => 9501,
],
'options' => [
],
'sampler' => BinarySampler::createAsAlwaysSample(),
],
'jaeger' => [
'driver' => \Hyperf\Tracer\Adapter\JaegerTracerFactory::class,
'name' => 'skeleton',
'options' => [
],
],
],
],
]);
$container = $this->getContainer($config);
$factory = new TracerFactory();
$this->assertInstanceOf(\Jaeger\Tracer::class, $factory($container));
}
protected function getContainer($config)
{
$container = Mockery::mock(Container::class);
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
$container->shouldReceive('get')->with(\Hyperf\Tracer\Adapter\HttpClientFactory::class)->andReturn(null);
$container->shouldReceive('has')->andReturn(false);
ApplicationContext::setContainer($container);
return $container;
}
}