auto root span creation (#1354)
* auto root span creation proof of concept for automatically creating a root span on startup. the obvious deficiencies are: - no idea of response values (status code etc) - does not capture exceptions * deptrac
This commit is contained in:
parent
69825d395a
commit
5276df3171
|
@ -380,6 +380,7 @@ return [
|
|||
'vendor/phpunit/phpunit/src',
|
||||
'vendor/google/protobuf/src',
|
||||
'vendor/ramsey/uuid/src',
|
||||
'vendor/nyholm/psr7-server/src',
|
||||
],
|
||||
|
||||
// A list of individual files to include in analysis
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"require": {
|
||||
"php": "^8.1",
|
||||
"google/protobuf": "^3.22 || ^4.0",
|
||||
"nyholm/psr7-server": "^1.1",
|
||||
"php-http/discovery": "^1.14",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-client-implementation": "^1.0",
|
||||
|
|
|
@ -105,6 +105,10 @@ deptrac:
|
|||
collectors:
|
||||
- type: className
|
||||
regex: ^Ramsey\\Uuid\\*
|
||||
- name: NyholmPsr7Server
|
||||
collectors:
|
||||
- type: className
|
||||
regex: ^Nyholm\\Psr7Server\\*
|
||||
|
||||
ruleset:
|
||||
Context:
|
||||
|
@ -134,6 +138,7 @@ deptrac:
|
|||
- HttpClients
|
||||
- SPI
|
||||
- RamseyUuid
|
||||
- NyholmPsr7Server
|
||||
Contrib:
|
||||
- +SDK
|
||||
- +OtelProto
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example;
|
||||
|
||||
use OpenTelemetry\API\Globals;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
|
||||
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
|
||||
putenv('OTEL_TRACES_EXPORTER=console');
|
||||
putenv('OTEL_METRICS_EXPORTER=none');
|
||||
putenv('OTEL_LOGS_EXPORTER=console');
|
||||
putenv('OTEL_PROPAGATORS=tracecontext');
|
||||
putenv('OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true');
|
||||
|
||||
//Usage: php -S localhost:8080 examples/traces/features/auto_root_span.php
|
||||
|
||||
require dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||
|
||||
Globals::loggerProvider()->getLogger('test')->emit(new LogRecord('I processed a request'));
|
||||
echo 'hello world!' . PHP_EOL;
|
|
@ -119,5 +119,6 @@ interface Defaults
|
|||
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = [];
|
||||
public const OTEL_PHP_LOGS_PROCESSOR = 'batch';
|
||||
public const OTEL_PHP_LOG_DESTINATION = 'default';
|
||||
public const OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN = 'false';
|
||||
public const OTEL_EXPERIMENTAL_CONFIG_FILE = 'sdk-config.yaml';
|
||||
}
|
||||
|
|
|
@ -140,5 +140,6 @@ interface Variables
|
|||
public const OTEL_PHP_INTERNAL_METRICS_ENABLED = 'OTEL_PHP_INTERNAL_METRICS_ENABLED'; //whether the SDK should emit its own metrics
|
||||
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = 'OTEL_PHP_DISABLED_INSTRUMENTATIONS';
|
||||
public const OTEL_PHP_EXCLUDED_URLS = 'OTEL_PHP_EXCLUDED_URLS';
|
||||
public const OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN = 'OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN';
|
||||
public const OTEL_EXPERIMENTAL_CONFIG_FILE = 'OTEL_EXPERIMENTAL_CONFIG_FILE';
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ final class ShutdownHandler
|
|||
// Push shutdown to end of queue
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentInternal
|
||||
register_shutdown_function(static function (array $handlers): void {
|
||||
foreach ($handlers as $handler) {
|
||||
foreach (array_reverse($handlers) as $handler) {
|
||||
$handler();
|
||||
}
|
||||
}, $handlers);
|
||||
|
|
|
@ -30,6 +30,7 @@ use OpenTelemetry\SDK\Logs\LoggerProviderFactory;
|
|||
use OpenTelemetry\SDK\Metrics\MeterProviderFactory;
|
||||
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
|
||||
use OpenTelemetry\SDK\Trace\AutoRootSpan;
|
||||
use OpenTelemetry\SDK\Trace\ExporterFactory;
|
||||
use OpenTelemetry\SDK\Trace\SamplerFactory;
|
||||
use OpenTelemetry\SDK\Trace\SpanProcessorFactory;
|
||||
|
@ -55,6 +56,14 @@ class SdkAutoloader
|
|||
}
|
||||
self::registerInstrumentations();
|
||||
|
||||
if (AutoRootSpan::isEnabled()) {
|
||||
$request = AutoRootSpan::createRequest();
|
||||
if ($request) {
|
||||
AutoRootSpan::create($request);
|
||||
AutoRootSpan::registerShutdownHandler();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -228,4 +237,5 @@ class SdkAutoloader
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Trace;
|
||||
|
||||
use Http\Discovery\Exception\NotFoundException;
|
||||
use Http\Discovery\Psr17FactoryDiscovery;
|
||||
use Nyholm\Psr7Server\ServerRequestCreator;
|
||||
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
|
||||
use OpenTelemetry\API\Globals;
|
||||
use OpenTelemetry\API\Trace\Span;
|
||||
use OpenTelemetry\API\Trace\SpanKind;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
|
||||
use OpenTelemetry\SemConv\TraceAttributes;
|
||||
use OpenTelemetry\SemConv\Version;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class AutoRootSpan
|
||||
{
|
||||
use LogsMessagesTrait;
|
||||
|
||||
public static function isEnabled(): bool
|
||||
{
|
||||
return
|
||||
!empty($_SERVER['REQUEST_METHOD'] ?? null)
|
||||
&& Configuration::getBoolean(Variables::OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
* @internal
|
||||
*/
|
||||
public static function create(ServerRequestInterface $request): void
|
||||
{
|
||||
$tracer = Globals::tracerProvider()->getTracer(
|
||||
'io.opentelemetry.php.auto-root-span',
|
||||
null,
|
||||
Version::VERSION_1_25_0->url(),
|
||||
);
|
||||
$parent = Globals::propagator()->extract($request->getHeaders());
|
||||
$startTime = array_key_exists('REQUEST_TIME_FLOAT', $request->getServerParams())
|
||||
? $request->getServerParams()['REQUEST_TIME_FLOAT']
|
||||
: (int) microtime(true);
|
||||
$span = $tracer->spanBuilder($request->getMethod())
|
||||
->setSpanKind(SpanKind::KIND_SERVER)
|
||||
->setStartTimestamp((int) ($startTime*1_000_000))
|
||||
->setParent($parent)
|
||||
->setAttribute(TraceAttributes::URL_FULL, (string) $request->getUri())
|
||||
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod())
|
||||
->setAttribute(TraceAttributes::HTTP_REQUEST_BODY_SIZE, $request->getHeaderLine('Content-Length'))
|
||||
->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->getHeaderLine('User-Agent'))
|
||||
->setAttribute(TraceAttributes::SERVER_ADDRESS, $request->getUri()->getHost())
|
||||
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort())
|
||||
->setAttribute(TraceAttributes::URL_SCHEME, $request->getUri()->getScheme())
|
||||
->setAttribute(TraceAttributes::URL_PATH, $request->getUri()->getPath())
|
||||
->startSpan();
|
||||
Context::storage()->attach($span->storeInContext($parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function createRequest(): ?ServerRequestInterface
|
||||
{
|
||||
assert(array_key_exists('REQUEST_METHOD', $_SERVER) && !empty($_SERVER['REQUEST_METHOD']));
|
||||
|
||||
try {
|
||||
$creator = new ServerRequestCreator(
|
||||
Psr17FactoryDiscovery::findServerRequestFactory(),
|
||||
Psr17FactoryDiscovery::findUriFactory(),
|
||||
Psr17FactoryDiscovery::findUploadedFileFactory(),
|
||||
Psr17FactoryDiscovery::findStreamFactory(),
|
||||
);
|
||||
|
||||
return $creator->fromGlobals();
|
||||
} catch (NotFoundException $e) {
|
||||
self::logError('Unable to initialize server request creator for auto root span creation', ['exception' => $e]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function registerShutdownHandler(): void
|
||||
{
|
||||
ShutdownHandler::register(self::shutdownHandler(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function shutdownHandler(): void
|
||||
{
|
||||
$scope = Context::storage()->scope();
|
||||
if (!$scope) {
|
||||
return;
|
||||
}
|
||||
$scope->detach();
|
||||
$span = Span::fromContext($scope->context());
|
||||
$span->end();
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-json": "*",
|
||||
"nyholm/psr7-server": "^1.1",
|
||||
"open-telemetry/api": "~1.0 || ~1.1",
|
||||
"open-telemetry/context": "^1.0",
|
||||
"open-telemetry/sem-conv": "^1.0",
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
--TEST--
|
||||
Auto root span creation
|
||||
--ENV--
|
||||
OTEL_PHP_AUTOLOAD_ENABLED=true
|
||||
OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true
|
||||
OTEL_TRACES_EXPORTER=console
|
||||
OTEL_METRICS_EXPORTER=none
|
||||
OTEL_LOGS_EXPORTER=console
|
||||
REQUEST_METHOD=GET
|
||||
REQUEST_URI=/foo?bar=baz
|
||||
REQUEST_SCHEME=https
|
||||
SERVER_NAME=example.com
|
||||
SERVER_PORT=8080
|
||||
HTTP_HOST=example.com:8080
|
||||
HTTP_USER_AGENT=my-user-agent/1.0
|
||||
REQUEST_TIME_FLOAT=1721706151.242976
|
||||
HTTP_TRACEPARENT=00-ff000000000000000000000000000041-ff00000000000041-01
|
||||
--FILE--
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
?>
|
||||
--EXPECTF--
|
||||
[
|
||||
{
|
||||
"name": "GET",
|
||||
"context": {
|
||||
"trace_id": "ff000000000000000000000000000041",
|
||||
"span_id": "%s",
|
||||
"trace_state": "",
|
||||
"trace_flags": 1
|
||||
},
|
||||
"resource": {%A
|
||||
},
|
||||
"parent_span_id": "ff00000000000041",
|
||||
"kind": "KIND_SERVER",
|
||||
"start": 1721706151242976,
|
||||
"end": %d,
|
||||
"attributes": {
|
||||
"url.full": "%s",
|
||||
"http.request.method": "GET",
|
||||
"http.request.body.size": "",
|
||||
"user_agent.original": "my-user-agent\/1.0",
|
||||
"server.address": "%S",
|
||||
"server.port": %d,
|
||||
"url.scheme": "https",
|
||||
"url.path": "\/foo"
|
||||
},
|
||||
"status": {
|
||||
"code": "Unset",
|
||||
"description": ""
|
||||
},
|
||||
"events": [],
|
||||
"links": [],
|
||||
"schema_url": "%s"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\SDK\Trace;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use OpenTelemetry\API\Instrumentation\Configurator;
|
||||
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
|
||||
use OpenTelemetry\API\Trace\Span;
|
||||
use OpenTelemetry\API\Trace\SpanBuilderInterface;
|
||||
use OpenTelemetry\API\Trace\SpanInterface;
|
||||
use OpenTelemetry\API\Trace\SpanKind;
|
||||
use OpenTelemetry\API\Trace\TracerInterface;
|
||||
use OpenTelemetry\API\Trace\TracerProviderInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\Context\ContextKeys;
|
||||
use OpenTelemetry\Context\ScopeInterface;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Trace\AutoRootSpan;
|
||||
use OpenTelemetry\Tests\TestState;
|
||||
use PHPUnit\Framework\Attributes\BackupGlobals;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(AutoRootSpan::class)]
|
||||
class AutoRootSpanTest extends TestCase
|
||||
{
|
||||
use TestState;
|
||||
|
||||
/** @var (TracerInterface&MockObject) */
|
||||
private TracerInterface $tracer;
|
||||
private ScopeInterface $scope;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$tracerProvider = $this->createMock(TracerProviderInterface::class);
|
||||
$this->tracer = $this->createMock(TracerInterface::class);
|
||||
$tracerProvider->method('getTracer')->willReturn($this->tracer);
|
||||
|
||||
$this->scope = Configurator::create()
|
||||
->withTracerProvider($tracerProvider)
|
||||
->withPropagator(new TraceContextPropagator())
|
||||
->activate();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->scope->detach();
|
||||
}
|
||||
|
||||
#[BackupGlobals(true)]
|
||||
#[DataProvider('enabledProvider')]
|
||||
public function test_is_enabled(string $enabled, ?string $method, bool $expected): void
|
||||
{
|
||||
$this->setEnvironmentVariable(Variables::OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN, $enabled);
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$this->assertSame($expected, AutoRootSpan::isEnabled());
|
||||
}
|
||||
|
||||
public static function enabledProvider(): array
|
||||
{
|
||||
return [
|
||||
['true', 'GET', true],
|
||||
['true', null, false],
|
||||
['true', '', false],
|
||||
['false', 'GET', false],
|
||||
];
|
||||
}
|
||||
|
||||
#[BackupGlobals(true)]
|
||||
public function test_create_request(): void
|
||||
{
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_SERVER['REQUEST_URI'] = '/foo';
|
||||
|
||||
$request = AutoRootSpan::createRequest();
|
||||
$this->assertNotNull($request);
|
||||
$this->assertSame('GET', $request->getMethod());
|
||||
$this->assertSame('/foo', $request->getUri()->getPath());
|
||||
}
|
||||
|
||||
public function test_create(): void
|
||||
{
|
||||
$body = 'hello otel';
|
||||
$traceId = 'ff000000000000000000000000000041';
|
||||
$spanId = 'ff00000000000041';
|
||||
$traceParent = '00-' . $traceId . '-' . $spanId . '-01';
|
||||
$request = new ServerRequest('POST', 'https://example.com/foo?bar=baz', ['traceparent' => $traceParent], $body);
|
||||
|
||||
$spanBuilder = $this->createMock(SpanBuilderInterface::class);
|
||||
$spanBuilder
|
||||
->expects($this->once())
|
||||
->method('setSpanKind')
|
||||
->with($this->equalTo(SpanKind::KIND_SERVER))
|
||||
->willReturnSelf();
|
||||
$spanBuilder
|
||||
->expects($this->once())
|
||||
->method('setStartTimestamp')
|
||||
->willReturnSelf();
|
||||
$spanBuilder
|
||||
->expects($this->once())
|
||||
->method('setParent')
|
||||
->with($this->callback(function (ContextInterface $parent) use ($traceId, $spanId) {
|
||||
$span = Span::fromContext($parent);
|
||||
$this->assertSame($traceId, $span->getContext()->getTraceId());
|
||||
$this->assertSame($spanId, $span->getContext()->getSpanId());
|
||||
|
||||
return true;
|
||||
}))
|
||||
->willReturnSelf();
|
||||
$spanBuilder
|
||||
->expects($this->atLeast(8))
|
||||
->method('setAttribute')
|
||||
->willReturnSelf();
|
||||
|
||||
$this->tracer
|
||||
->expects($this->once())
|
||||
->method('spanBuilder')
|
||||
->with($this->equalTo('POST'))
|
||||
->willReturn($spanBuilder);
|
||||
|
||||
AutoRootSpan::create($request);
|
||||
|
||||
$scope = Context::storage()->scope();
|
||||
$this->assertNotNull($scope);
|
||||
$scope->detach();
|
||||
}
|
||||
|
||||
public function test_shutdown_handler(): void
|
||||
{
|
||||
$this->setEnvironmentVariable('OTEL_PHP_DEBUG_SCOPES_DISABLED', 'true');
|
||||
$span = $this->createMock(SpanInterface::class);
|
||||
$span
|
||||
->expects($this->once())
|
||||
->method('end');
|
||||
Context::getCurrent()->with(ContextKeys::span(), $span)->activate();
|
||||
|
||||
AutoRootSpan::shutdownHandler();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue