• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FriendsOfOpenTelemetry / opentelemetry-bundle / 8190855464

07 Mar 2024 03:41PM UTC coverage: 86.11% (-0.5%) from 86.612%
8190855464

push

github

gaelreyrol
feat(instrumentation): log scope & span usages

85 of 112 new or added lines in 17 files covered. (75.89%)

7 existing lines in 5 files now uncovered.

1959 of 2275 relevant lines covered (86.11%)

6.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.74
/src/Instrumentation/Symfony/HttpClient/TraceableHttpClient.php
1
<?php
2

3
namespace FriendsOfOpenTelemetry\OpenTelemetryBundle\Instrumentation\Symfony\HttpClient;
4

5
use Nyholm\Psr7\Uri;
6
use OpenTelemetry\API\Trace\SpanInterface;
7
use OpenTelemetry\API\Trace\SpanKind;
8
use OpenTelemetry\API\Trace\StatusCode;
9
use OpenTelemetry\API\Trace\TracerInterface;
10
use OpenTelemetry\Context\Context;
11
use OpenTelemetry\Context\ScopeInterface;
12
use OpenTelemetry\SemConv\TraceAttributes;
13
use Psr\Log\LoggerAwareInterface;
14
use Psr\Log\LoggerInterface;
15
use Symfony\Component\HttpClient\Response\ResponseStream;
16
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
17
use Symfony\Contracts\HttpClient\HttpClientInterface;
18
use Symfony\Contracts\HttpClient\ResponseInterface;
19
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
20
use Symfony\Contracts\Service\ResetInterface;
21

22
final class TraceableHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
23
{
24
    private ?ScopeInterface $scope = null;
25

26
    public function __construct(
27
        private HttpClientInterface $client,
28
        private readonly TracerInterface $tracer,
29
        private ?LoggerInterface $logger = null,
30
    ) {
31
    }
6✔
32

33
    /**
34
     * @param array<mixed> $options
35
     */
36
    public function request(string $method, string $url, array $options = []): ResponseInterface
37
    {
38
        $scope = Context::storage()->scope();
4✔
39
        if (null !== $scope) {
4✔
NEW
40
            $this->logger?->debug(sprintf('Using scope "%s"', spl_object_id($scope)));
×
41
        }
42
        $span = null;
4✔
43

44
        try {
45
            $uri = new Uri($url);
4✔
46

47
            $spanBuilder = $this->tracer
4✔
48
                ->spanBuilder('http.client')
4✔
49
                ->setSpanKind(SpanKind::KIND_CLIENT)
4✔
50
                ->setAttribute(TraceAttributes::URL_FULL, $url)
4✔
51
                ->setAttribute(TraceAttributes::URL_SCHEME, $uri->getScheme())
4✔
52
                ->setAttribute(TraceAttributes::URL_PATH, $uri->getPath())
4✔
53
                ->setAttribute(TraceAttributes::URL_QUERY, $uri->getQuery())
4✔
54
                ->setAttribute(TraceAttributes::URL_FRAGMENT, $uri->getFragment())
4✔
55
                ->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $method)
4✔
56
            ;
4✔
57

58
            $span = $spanBuilder->setParent($scope?->context())->startSpan();
4✔
59

60
            $this->logger?->debug(sprintf('Starting span "%s"', $span->getContext()->getSpanId()));
4✔
61

62
            if (null === $scope && null === $this->scope) {
4✔
63
                $this->scope = $span->storeInContext(Context::getCurrent())->activate();
4✔
64
                $this->logger?->debug(sprintf('No active scope, activating new scope "%s"', spl_object_id($this->scope)));
4✔
65
            }
66

67
            return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $span);
4✔
68
        } catch (ExceptionInterface $exception) {
×
69
            $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
×
70
            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
×
71
            throw $exception;
×
72
        } finally {
73
            if (null !== $this->scope) {
4✔
74
                $this->logger?->debug(sprintf('Detaching scope "%s"', spl_object_id($this->scope)));
4✔
75
                $this->scope->detach();
4✔
76
                $this->scope = null;
4✔
77
            }
78
            if ($span instanceof SpanInterface) {
4✔
79
                $this->logger?->debug(sprintf('Ending span "%s"', $span->getContext()->getSpanId()));
4✔
80
                $span->end();
4✔
81
            }
82
        }
83
    }
84

85
    public function stream(iterable|ResponseInterface $responses, ?float $timeout = null): ResponseStreamInterface
86
    {
87
        if ($responses instanceof TraceableResponse) {
1✔
88
            $responses = [$responses];
1✔
89
        } elseif (!is_iterable($responses)) {
×
90
            throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
×
91
        }
92

93
        return new ResponseStream(TraceableResponse::stream($this->client, $responses, $timeout));
1✔
94
    }
95

96
    /**
97
     * @param array<mixed> $options
98
     */
99
    public function withOptions(array $options): static
100
    {
101
        $clone = clone $this;
×
102
        $clone->client = $this->client->withOptions($options);
×
103

104
        return $clone;
×
105
    }
106

107
    public function setLogger(LoggerInterface $logger): void
108
    {
109
        if ($this->client instanceof LoggerAwareInterface) {
×
110
            $this->client->setLogger($logger);
×
111
        }
NEW
112
        $this->logger = $logger;
×
113
    }
114

115
    public function reset(): void
116
    {
117
        if ($this->client instanceof ResetInterface) {
2✔
118
            $this->client->reset();
2✔
119
        }
120
    }
121
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc