Skip to content

Commit 5276df3

Browse files
authored
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
1 parent 69825d3 commit 5276df3

File tree

12 files changed

+352
-1
lines changed

12 files changed

+352
-1
lines changed

.phan/config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@
380380
'vendor/phpunit/phpunit/src',
381381
'vendor/google/protobuf/src',
382382
'vendor/ramsey/uuid/src',
383+
'vendor/nyholm/psr7-server/src',
383384
],
384385

385386
// A list of individual files to include in analysis

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"require": {
1010
"php": "^8.1",
1111
"google/protobuf": "^3.22 || ^4.0",
12+
"nyholm/psr7-server": "^1.1",
1213
"php-http/discovery": "^1.14",
1314
"psr/http-client": "^1.0",
1415
"psr/http-client-implementation": "^1.0",

deptrac.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ deptrac:
105105
collectors:
106106
- type: className
107107
regex: ^Ramsey\\Uuid\\*
108+
- name: NyholmPsr7Server
109+
collectors:
110+
- type: className
111+
regex: ^Nyholm\\Psr7Server\\*
108112

109113
ruleset:
110114
Context:
@@ -134,6 +138,7 @@ deptrac:
134138
- HttpClients
135139
- SPI
136140
- RamseyUuid
141+
- NyholmPsr7Server
137142
Contrib:
138143
- +SDK
139144
- +OtelProto
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Globals;
8+
use OpenTelemetry\API\Logs\LogRecord;
9+
10+
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
11+
putenv('OTEL_TRACES_EXPORTER=console');
12+
putenv('OTEL_METRICS_EXPORTER=none');
13+
putenv('OTEL_LOGS_EXPORTER=console');
14+
putenv('OTEL_PROPAGATORS=tracecontext');
15+
putenv('OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true');
16+
17+
//Usage: php -S localhost:8080 examples/traces/features/auto_root_span.php
18+
19+
require dirname(__DIR__, 3) . '/vendor/autoload.php';
20+
21+
Globals::loggerProvider()->getLogger('test')->emit(new LogRecord('I processed a request'));
22+
echo 'hello world!' . PHP_EOL;

src/SDK/Common/Configuration/Defaults.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,6 @@ interface Defaults
119119
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = [];
120120
public const OTEL_PHP_LOGS_PROCESSOR = 'batch';
121121
public const OTEL_PHP_LOG_DESTINATION = 'default';
122+
public const OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN = 'false';
122123
public const OTEL_EXPERIMENTAL_CONFIG_FILE = 'sdk-config.yaml';
123124
}

src/SDK/Common/Configuration/Variables.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,6 @@ interface Variables
140140
public const OTEL_PHP_INTERNAL_METRICS_ENABLED = 'OTEL_PHP_INTERNAL_METRICS_ENABLED'; //whether the SDK should emit its own metrics
141141
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = 'OTEL_PHP_DISABLED_INSTRUMENTATIONS';
142142
public const OTEL_PHP_EXCLUDED_URLS = 'OTEL_PHP_EXCLUDED_URLS';
143+
public const OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN = 'OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN';
143144
public const OTEL_EXPERIMENTAL_CONFIG_FILE = 'OTEL_EXPERIMENTAL_CONFIG_FILE';
144145
}

src/SDK/Common/Util/ShutdownHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private static function registerShutdownFunction(): void
7272
// Push shutdown to end of queue
7373
// @phan-suppress-next-line PhanTypeMismatchArgumentInternal
7474
register_shutdown_function(static function (array $handlers): void {
75-
foreach ($handlers as $handler) {
75+
foreach (array_reverse($handlers) as $handler) {
7676
$handler();
7777
}
7878
}, $handlers);

src/SDK/SdkAutoloader.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use OpenTelemetry\SDK\Metrics\MeterProviderFactory;
3131
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
3232
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
33+
use OpenTelemetry\SDK\Trace\AutoRootSpan;
3334
use OpenTelemetry\SDK\Trace\ExporterFactory;
3435
use OpenTelemetry\SDK\Trace\SamplerFactory;
3536
use OpenTelemetry\SDK\Trace\SpanProcessorFactory;
@@ -55,6 +56,14 @@ public static function autoload(): bool
5556
}
5657
self::registerInstrumentations();
5758

59+
if (AutoRootSpan::isEnabled()) {
60+
$request = AutoRootSpan::createRequest();
61+
if ($request) {
62+
AutoRootSpan::create($request);
63+
AutoRootSpan::registerShutdownHandler();
64+
}
65+
}
66+
5867
return true;
5968
}
6069

@@ -228,4 +237,5 @@ public static function isExcludedUrl(): bool
228237

229238
return false;
230239
}
240+
231241
}

src/SDK/Trace/AutoRootSpan.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace;
6+
7+
use Http\Discovery\Exception\NotFoundException;
8+
use Http\Discovery\Psr17FactoryDiscovery;
9+
use Nyholm\Psr7Server\ServerRequestCreator;
10+
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
11+
use OpenTelemetry\API\Globals;
12+
use OpenTelemetry\API\Trace\Span;
13+
use OpenTelemetry\API\Trace\SpanKind;
14+
use OpenTelemetry\Context\Context;
15+
use OpenTelemetry\SDK\Common\Configuration\Configuration;
16+
use OpenTelemetry\SDK\Common\Configuration\Variables;
17+
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
18+
use OpenTelemetry\SemConv\TraceAttributes;
19+
use OpenTelemetry\SemConv\Version;
20+
use Psr\Http\Message\ServerRequestInterface;
21+
22+
class AutoRootSpan
23+
{
24+
use LogsMessagesTrait;
25+
26+
public static function isEnabled(): bool
27+
{
28+
return
29+
!empty($_SERVER['REQUEST_METHOD'] ?? null)
30+
&& Configuration::getBoolean(Variables::OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN);
31+
}
32+
33+
/**
34+
* @psalm-suppress ArgumentTypeCoercion
35+
* @internal
36+
*/
37+
public static function create(ServerRequestInterface $request): void
38+
{
39+
$tracer = Globals::tracerProvider()->getTracer(
40+
'io.opentelemetry.php.auto-root-span',
41+
null,
42+
Version::VERSION_1_25_0->url(),
43+
);
44+
$parent = Globals::propagator()->extract($request->getHeaders());
45+
$startTime = array_key_exists('REQUEST_TIME_FLOAT', $request->getServerParams())
46+
? $request->getServerParams()['REQUEST_TIME_FLOAT']
47+
: (int) microtime(true);
48+
$span = $tracer->spanBuilder($request->getMethod())
49+
->setSpanKind(SpanKind::KIND_SERVER)
50+
->setStartTimestamp((int) ($startTime*1_000_000))
51+
->setParent($parent)
52+
->setAttribute(TraceAttributes::URL_FULL, (string) $request->getUri())
53+
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod())
54+
->setAttribute(TraceAttributes::HTTP_REQUEST_BODY_SIZE, $request->getHeaderLine('Content-Length'))
55+
->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->getHeaderLine('User-Agent'))
56+
->setAttribute(TraceAttributes::SERVER_ADDRESS, $request->getUri()->getHost())
57+
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort())
58+
->setAttribute(TraceAttributes::URL_SCHEME, $request->getUri()->getScheme())
59+
->setAttribute(TraceAttributes::URL_PATH, $request->getUri()->getPath())
60+
->startSpan();
61+
Context::storage()->attach($span->storeInContext($parent));
62+
}
63+
64+
/**
65+
* @internal
66+
*/
67+
public static function createRequest(): ?ServerRequestInterface
68+
{
69+
assert(array_key_exists('REQUEST_METHOD', $_SERVER) && !empty($_SERVER['REQUEST_METHOD']));
70+
71+
try {
72+
$creator = new ServerRequestCreator(
73+
Psr17FactoryDiscovery::findServerRequestFactory(),
74+
Psr17FactoryDiscovery::findUriFactory(),
75+
Psr17FactoryDiscovery::findUploadedFileFactory(),
76+
Psr17FactoryDiscovery::findStreamFactory(),
77+
);
78+
79+
return $creator->fromGlobals();
80+
} catch (NotFoundException $e) {
81+
self::logError('Unable to initialize server request creator for auto root span creation', ['exception' => $e]);
82+
}
83+
84+
return null;
85+
}
86+
87+
/**
88+
* @internal
89+
*/
90+
public static function registerShutdownHandler(): void
91+
{
92+
ShutdownHandler::register(self::shutdownHandler(...));
93+
}
94+
95+
/**
96+
* @internal
97+
*/
98+
public static function shutdownHandler(): void
99+
{
100+
$scope = Context::storage()->scope();
101+
if (!$scope) {
102+
return;
103+
}
104+
$scope->detach();
105+
$span = Span::fromContext($scope->context());
106+
$span->end();
107+
}
108+
}

src/SDK/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"require": {
2020
"php": "^8.1",
2121
"ext-json": "*",
22+
"nyholm/psr7-server": "^1.1",
2223
"open-telemetry/api": "~1.0 || ~1.1",
2324
"open-telemetry/context": "^1.0",
2425
"open-telemetry/sem-conv": "^1.0",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
Auto root span creation
3+
--ENV--
4+
OTEL_PHP_AUTOLOAD_ENABLED=true
5+
OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true
6+
OTEL_TRACES_EXPORTER=console
7+
OTEL_METRICS_EXPORTER=none
8+
OTEL_LOGS_EXPORTER=console
9+
REQUEST_METHOD=GET
10+
REQUEST_URI=/foo?bar=baz
11+
REQUEST_SCHEME=https
12+
SERVER_NAME=example.com
13+
SERVER_PORT=8080
14+
HTTP_HOST=example.com:8080
15+
HTTP_USER_AGENT=my-user-agent/1.0
16+
REQUEST_TIME_FLOAT=1721706151.242976
17+
HTTP_TRACEPARENT=00-ff000000000000000000000000000041-ff00000000000041-01
18+
--FILE--
19+
<?php
20+
require_once 'vendor/autoload.php';
21+
?>
22+
--EXPECTF--
23+
[
24+
{
25+
"name": "GET",
26+
"context": {
27+
"trace_id": "ff000000000000000000000000000041",
28+
"span_id": "%s",
29+
"trace_state": "",
30+
"trace_flags": 1
31+
},
32+
"resource": {%A
33+
},
34+
"parent_span_id": "ff00000000000041",
35+
"kind": "KIND_SERVER",
36+
"start": 1721706151242976,
37+
"end": %d,
38+
"attributes": {
39+
"url.full": "%s",
40+
"http.request.method": "GET",
41+
"http.request.body.size": "",
42+
"user_agent.original": "my-user-agent\/1.0",
43+
"server.address": "%S",
44+
"server.port": %d,
45+
"url.scheme": "https",
46+
"url.path": "\/foo"
47+
},
48+
"status": {
49+
"code": "Unset",
50+
"description": ""
51+
},
52+
"events": [],
53+
"links": [],
54+
"schema_url": "%s"
55+
}
56+
]

0 commit comments

Comments
 (0)