Skip to content

Commit 1510539

Browse files
authored
Add PSR18 http client auto instrumentation (open-telemetry#78)
* Add PSR18 http client auto instrumentation * Update for API changes
1 parent 3335913 commit 1510539

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\Psr7\Request;
7+
use OpenTelemetry\API\Common\Instrumentation;
8+
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
9+
use OpenTelemetry\SDK\Common\Time\ClockFactory;
10+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
11+
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
12+
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
13+
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
14+
use OpenTelemetry\SDK\Trace\TracerProvider;
15+
16+
require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
17+
require_once dirname(__DIR__, 3) . '/src/Instrumentation/Psr18/client_tracing.php';
18+
19+
$tracerProvider = new TracerProvider(
20+
new BatchSpanProcessor(new ConsoleSpanExporter(), ClockFactory::getDefault()),
21+
new AlwaysOnSampler(),
22+
ResourceInfoFactory::emptyResource(),
23+
);
24+
25+
$scope = Instrumentation\Configurator::create()
26+
->withTracerProvider($tracerProvider)
27+
->withPropagator(TraceContextPropagator::getInstance())
28+
->activate();
29+
30+
try {
31+
$client = new Client();
32+
33+
$response = $client->sendRequest(new Request('GET', 'https://postman-echo.com/get'));
34+
echo json_encode(json_decode($response->getBody()->getContents()), JSON_PRETTY_PRINT), PHP_EOL;
35+
} finally {
36+
$scope->detach();
37+
$tracerProvider->shutdown();
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Instrumentation\Psr18;
6+
7+
use function assert;
8+
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
9+
use Psr\Http\Message\RequestInterface;
10+
11+
/**
12+
* @internal
13+
*/
14+
enum HeadersPropagator implements PropagationSetterInterface
15+
{
16+
case Instance;
17+
18+
public function set(&$carrier, string $key, string $value): void
19+
{
20+
assert($carrier instanceof RequestInterface);
21+
22+
$carrier = $carrier->withAddedHeader($key, $value);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Instrumentation\Psr18;
6+
7+
use function get_cfg_var;
8+
use OpenTelemetry\API\Common\Instrumentation;
9+
use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
10+
use OpenTelemetry\API\Trace\Span;
11+
use OpenTelemetry\API\Trace\SpanKind;
12+
use OpenTelemetry\API\Trace\StatusCode;
13+
use OpenTelemetry\Context\Context;
14+
use function OpenTelemetry\Instrumentation\hook;
15+
use OpenTelemetry\SemConv\TraceAttributes;
16+
use Psr\Http\Client\ClientInterface;
17+
use Psr\Http\Message\RequestInterface;
18+
use Psr\Http\Message\ResponseInterface;
19+
use function sprintf;
20+
use function strtolower;
21+
use Throwable;
22+
23+
hook(
24+
ClientInterface::class,
25+
'sendRequest',
26+
static function (ClientInterface $client, array $params, string $class, string $function, ?string $filename, ?int $lineno): ?array {
27+
$request = $params[0] ?? null;
28+
if (!$request instanceof RequestInterface) {
29+
Context::storage()->attach(Context::getCurrent());
30+
31+
return null;
32+
}
33+
34+
static $instrumentation;
35+
$instrumentation ??= new CachedInstrumentation('io.opentelemetry.contrib.php.psr18', schemaUrl: TraceAttributes::SCHEMA_URL);
36+
$propagator = Instrumentation\Globals::propagator();
37+
$parentContext = Context::getCurrent();
38+
39+
$spanBuilder = $instrumentation
40+
->tracer()
41+
->spanBuilder(sprintf('HTTP %s', $request->getMethod()))
42+
->setParent($parentContext)
43+
->setSpanKind(SpanKind::KIND_CLIENT)
44+
->setAttribute(TraceAttributes::HTTP_URL, (string) $request->getUri())
45+
->setAttribute(TraceAttributes::HTTP_METHOD, $request->getMethod())
46+
->setAttribute(TraceAttributes::HTTP_FLAVOR, $request->getProtocolVersion())
47+
->setAttribute(TraceAttributes::HTTP_USER_AGENT, $request->getHeaderLine('User-Agent'))
48+
->setAttribute(TraceAttributes::HTTP_REQUEST_CONTENT_LENGTH, $request->getHeaderLine('Content-Length'))
49+
->setAttribute(TraceAttributes::NET_PEER_NAME, $request->getUri()->getHost())
50+
->setAttribute(TraceAttributes::NET_PEER_PORT, $request->getUri()->getPort())
51+
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
52+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
53+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
54+
->setAttribute(TraceAttributes::CODE_LINENO, $lineno)
55+
;
56+
57+
foreach ($propagator->fields() as $field) {
58+
$request = $request->withoutHeader($field);
59+
}
60+
foreach ((array) (get_cfg_var('otel.instrumentation.http.request_headers') ?: []) as $header) {
61+
if ($request->hasHeader($header)) {
62+
$spanBuilder->setAttribute(sprintf('http.request.header.%s', strtr(strtolower($header), ['-' => '_'])), $request->getHeader($header));
63+
}
64+
}
65+
66+
$span = $spanBuilder->startSpan();
67+
$context = $span->storeInContext($parentContext);
68+
$propagator->inject($request, HeadersPropagator::Instance, $context);
69+
70+
Context::storage()->attach($context);
71+
72+
return [$request];
73+
},
74+
static function (ClientInterface $client, array $params, ?ResponseInterface $response, ?Throwable $exception): void {
75+
$scope = Context::storage()->scope();
76+
$scope?->detach();
77+
78+
if (!$scope || $scope->context() === Context::getCurrent()) {
79+
return;
80+
}
81+
82+
$span = Span::fromContext($scope->context());
83+
84+
if ($response) {
85+
$span->setAttribute(TraceAttributes::HTTP_STATUS_CODE, $response->getStatusCode());
86+
$span->setAttribute(TraceAttributes::HTTP_FLAVOR, $response->getProtocolVersion());
87+
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_CONTENT_LENGTH, $response->getHeaderLine('Content-Length'));
88+
89+
foreach ((array) (get_cfg_var('otel.instrumentation.http.response_headers') ?: []) as $header) {
90+
if ($response->hasHeader($header)) {
91+
$span->setAttribute(sprintf('http.response.header.%s', strtr(strtolower($header), ['-' => '_'])), $response->getHeader($header));
92+
}
93+
}
94+
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) {
95+
$span->setStatus(StatusCode::STATUS_ERROR);
96+
}
97+
}
98+
if ($exception) {
99+
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
100+
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
101+
}
102+
103+
$span->end();
104+
},
105+
);

0 commit comments

Comments
 (0)