Skip to content

Commit

Permalink
Add PSR18 http client auto instrumentation (#78)
Browse files Browse the repository at this point in the history
* Add PSR18 http client auto instrumentation
* Update for API changes
  • Loading branch information
Nevay authored Nov 29, 2022
1 parent 3335913 commit 1510539
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
38 changes: 38 additions & 0 deletions examples/instrumentation/Psr18/request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use OpenTelemetry\API\Common\Instrumentation;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
require_once dirname(__DIR__, 3) . '/src/Instrumentation/Psr18/client_tracing.php';

$tracerProvider = new TracerProvider(
new BatchSpanProcessor(new ConsoleSpanExporter(), ClockFactory::getDefault()),
new AlwaysOnSampler(),
ResourceInfoFactory::emptyResource(),
);

$scope = Instrumentation\Configurator::create()
->withTracerProvider($tracerProvider)
->withPropagator(TraceContextPropagator::getInstance())
->activate();

try {
$client = new Client();

$response = $client->sendRequest(new Request('GET', 'https://postman-echo.com/get'));
echo json_encode(json_decode($response->getBody()->getContents()), JSON_PRETTY_PRINT), PHP_EOL;
} finally {
$scope->detach();
$tracerProvider->shutdown();
}
24 changes: 24 additions & 0 deletions src/Instrumentation/Psr18/HeadersPropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Instrumentation\Psr18;

use function assert;
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
use Psr\Http\Message\RequestInterface;

/**
* @internal
*/
enum HeadersPropagator implements PropagationSetterInterface
{
case Instance;

public function set(&$carrier, string $key, string $value): void
{
assert($carrier instanceof RequestInterface);

$carrier = $carrier->withAddedHeader($key, $value);
}
}
105 changes: 105 additions & 0 deletions src/Instrumentation/Psr18/client_tracing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Instrumentation\Psr18;

use function get_cfg_var;
use OpenTelemetry\API\Common\Instrumentation;
use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\SemConv\TraceAttributes;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use function sprintf;
use function strtolower;
use Throwable;

hook(
ClientInterface::class,
'sendRequest',
static function (ClientInterface $client, array $params, string $class, string $function, ?string $filename, ?int $lineno): ?array {
$request = $params[0] ?? null;
if (!$request instanceof RequestInterface) {
Context::storage()->attach(Context::getCurrent());

return null;
}

static $instrumentation;
$instrumentation ??= new CachedInstrumentation('io.opentelemetry.contrib.php.psr18', schemaUrl: TraceAttributes::SCHEMA_URL);
$propagator = Instrumentation\Globals::propagator();
$parentContext = Context::getCurrent();

$spanBuilder = $instrumentation
->tracer()
->spanBuilder(sprintf('HTTP %s', $request->getMethod()))
->setParent($parentContext)
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute(TraceAttributes::HTTP_URL, (string) $request->getUri())
->setAttribute(TraceAttributes::HTTP_METHOD, $request->getMethod())
->setAttribute(TraceAttributes::HTTP_FLAVOR, $request->getProtocolVersion())
->setAttribute(TraceAttributes::HTTP_USER_AGENT, $request->getHeaderLine('User-Agent'))
->setAttribute(TraceAttributes::HTTP_REQUEST_CONTENT_LENGTH, $request->getHeaderLine('Content-Length'))
->setAttribute(TraceAttributes::NET_PEER_NAME, $request->getUri()->getHost())
->setAttribute(TraceAttributes::NET_PEER_PORT, $request->getUri()->getPort())
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
->setAttribute(TraceAttributes::CODE_LINENO, $lineno)
;

foreach ($propagator->fields() as $field) {
$request = $request->withoutHeader($field);
}
foreach ((array) (get_cfg_var('otel.instrumentation.http.request_headers') ?: []) as $header) {
if ($request->hasHeader($header)) {
$spanBuilder->setAttribute(sprintf('http.request.header.%s', strtr(strtolower($header), ['-' => '_'])), $request->getHeader($header));
}
}

$span = $spanBuilder->startSpan();
$context = $span->storeInContext($parentContext);
$propagator->inject($request, HeadersPropagator::Instance, $context);

Context::storage()->attach($context);

return [$request];
},
static function (ClientInterface $client, array $params, ?ResponseInterface $response, ?Throwable $exception): void {
$scope = Context::storage()->scope();
$scope?->detach();

if (!$scope || $scope->context() === Context::getCurrent()) {
return;
}

$span = Span::fromContext($scope->context());

if ($response) {
$span->setAttribute(TraceAttributes::HTTP_STATUS_CODE, $response->getStatusCode());
$span->setAttribute(TraceAttributes::HTTP_FLAVOR, $response->getProtocolVersion());
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_CONTENT_LENGTH, $response->getHeaderLine('Content-Length'));

foreach ((array) (get_cfg_var('otel.instrumentation.http.response_headers') ?: []) as $header) {
if ($response->hasHeader($header)) {
$span->setAttribute(sprintf('http.response.header.%s', strtr(strtolower($header), ['-' => '_'])), $response->getHeader($header));
}
}
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) {
$span->setStatus(StatusCode::STATUS_ERROR);
}
}
if ($exception) {
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
}

$span->end();
},
);

0 comments on commit 1510539

Please sign in to comment.