From 1510539dd03e354745731b468308bac5bb0c324c Mon Sep 17 00:00:00 2001 From: Tobias Bachert Date: Tue, 29 Nov 2022 23:57:11 +0100 Subject: [PATCH] Add PSR18 http client auto instrumentation (#78) * Add PSR18 http client auto instrumentation * Update for API changes --- examples/instrumentation/Psr18/request.php | 38 +++++++ .../Psr18/HeadersPropagator.php | 24 ++++ src/Instrumentation/Psr18/client_tracing.php | 105 ++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 examples/instrumentation/Psr18/request.php create mode 100644 src/Instrumentation/Psr18/HeadersPropagator.php create mode 100644 src/Instrumentation/Psr18/client_tracing.php diff --git a/examples/instrumentation/Psr18/request.php b/examples/instrumentation/Psr18/request.php new file mode 100644 index 00000000..663198de --- /dev/null +++ b/examples/instrumentation/Psr18/request.php @@ -0,0 +1,38 @@ +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(); +} diff --git a/src/Instrumentation/Psr18/HeadersPropagator.php b/src/Instrumentation/Psr18/HeadersPropagator.php new file mode 100644 index 00000000..25ba14da --- /dev/null +++ b/src/Instrumentation/Psr18/HeadersPropagator.php @@ -0,0 +1,24 @@ +withAddedHeader($key, $value); + } +} diff --git a/src/Instrumentation/Psr18/client_tracing.php b/src/Instrumentation/Psr18/client_tracing.php new file mode 100644 index 00000000..7197767b --- /dev/null +++ b/src/Instrumentation/Psr18/client_tracing.php @@ -0,0 +1,105 @@ +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(); + }, +);