mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
[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:
parent
47e53de968
commit
fcad7ce6bf
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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": {
|
||||
|
@ -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),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -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;
|
87
src/tracer/src/Adapter/JaegerTracerFactory.php
Normal file
87
src/tracer/src/Adapter/JaegerTracerFactory.php
Normal 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);
|
||||
}
|
||||
}
|
57
src/tracer/src/Adapter/JaegerTracingFactory.php
Normal file
57
src/tracer/src/Adapter/JaegerTracingFactory.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
],
|
||||
|
21
src/tracer/src/Contract/NamedFactoryInterface.php
Normal file
21
src/tracer/src/Contract/NamedFactoryInterface.php
Normal 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);
|
||||
}
|
17
src/tracer/src/Exception/InvalidArgumentException.php
Normal file
17
src/tracer/src/Exception/InvalidArgumentException.php
Normal 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
|
||||
{
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
60
src/tracer/src/SpanStarter.php
Normal file
60
src/tracer/src/SpanStarter.php
Normal 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;
|
||||
}
|
||||
}
|
56
src/tracer/src/TracerFactory.php
Normal file
56
src/tracer/src/TracerFactory.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
144
src/tracer/tests/TracerFactoryTest.php
Normal file
144
src/tracer/tests/TracerFactoryTest.php
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user