Skip to content

Commit

Permalink
auto root span creation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
brettmc committed Jul 23, 2024
1 parent 4ed0d87 commit f38cbe7
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 23 deletions.
1 change: 1 addition & 0 deletions .phan/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
'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
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"require": {
"php": "^8.1",
"google/protobuf": "^3.22",
"nyholm/psr7-server": "^1.1",
"php-http/discovery": "^1.14",
"psr/http-client": "^1.0",
"psr/http-client-implementation": "^1.0",
Expand Down
22 changes: 22 additions & 0 deletions examples/traces/features/auto_root_span.php
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions src/SDK/Common/Configuration/Defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@ 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';
}
1 change: 1 addition & 0 deletions src/SDK/Common/Configuration/Variables.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,5 @@ 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';
}
2 changes: 1 addition & 1 deletion src/SDK/Common/Util/ShutdownHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private static function registerShutdownFunction(): void
// 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);
Expand Down
30 changes: 8 additions & 22 deletions src/SDK/SdkAutoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;
Expand Down Expand Up @@ -64,31 +65,15 @@ public static function autoload(): bool
;
});

return true;
}

/**
* Test whether a request URI is set, and if it matches the excluded urls configuration option
*
* @internal
*/
public static function isIgnoredUrl(): bool
{
$ignoreUrls = Configuration::getList(Variables::OTEL_PHP_EXCLUDED_URLS, []);
if ($ignoreUrls === []) {
return false;
}
$url = $_SERVER['REQUEST_URI'] ?? null;
if (!$url) {
return false;
}
foreach ($ignoreUrls as $ignore) {
if (preg_match(sprintf('|%s|', $ignore), (string) $url) === 1) {
return true;
if (AutoRootSpan::isEnabled()) {
$request = AutoRootSpan::createRequest();
if ($request) {
AutoRootSpan::create($request);
AutoRootSpan::registerShutdownHandler();
}
}

return false;
return true;
}

/**
Expand Down Expand Up @@ -129,4 +114,5 @@ public static function isExcludedUrl(): bool

return false;
}

}
108 changes: 108 additions & 0 deletions src/SDK/Trace/AutoRootSpan.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
1 change: 1 addition & 0 deletions src/SDK/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
56 changes: 56 additions & 0 deletions tests/Integration/SDK/Trace/test_auto_root_span_creation.phpt
Original file line number Diff line number Diff line change
@@ -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"
}
]
Loading

0 comments on commit f38cbe7

Please sign in to comment.