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

FriendsOfOpenTelemetry / opentelemetry-bundle / 24230406526

10 Apr 2026 06:48AM UTC coverage: 91.667%. First build
24230406526

Pull #206

github

web-flow
Merge 23440d902 into 8127daa50
Pull Request #206: chore: update dependencies, CI, and tracing sem conv across codebase

43 of 45 new or added lines in 9 files covered. (95.56%)

2255 of 2460 relevant lines covered (91.67%)

59.17 hits per line

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

65.91
/src/Instrumentation/Symfony/HttpClient/TraceableResponse.php
1
<?php
2

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

5
use OpenTelemetry\API\Trace\SpanInterface;
6
use OpenTelemetry\API\Trace\StatusCode;
7
use OpenTelemetry\SemConv\Attributes\HttpAttributes;
8
use OpenTelemetry\SemConv\Incubating\Attributes\HttpIncubatingAttributes;
9
use Psr\Log\LoggerInterface;
10
use Symfony\Component\HttpClient\Response\StreamableInterface;
11
use Symfony\Component\HttpClient\Response\StreamWrapper;
12
use Symfony\Contracts\HttpClient\ChunkInterface;
13
use Symfony\Contracts\HttpClient\HttpClientInterface;
14
use Symfony\Contracts\HttpClient\ResponseInterface;
15

16
final class TraceableResponse implements ResponseInterface, StreamableInterface
17
{
18
    public function __construct(
19
        public readonly HttpClientInterface $client,
20
        public readonly ResponseInterface $response,
21
        public readonly ?SpanInterface $span,
22
        public readonly ?LoggerInterface $logger = null,
23
    ) {
24
    }
16✔
25

26
    public function __sleep(): array
27
    {
28
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
×
29
    }
30

31
    public function __wakeup(): void
32
    {
33
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
×
34
    }
35

36
    public function __destruct()
37
    {
38
        try {
39
            if (method_exists($this->response, '__destruct')) {
16✔
40
                $this->response->__destruct();
16✔
41
            }
42
        } finally {
43
            $this->endSpan();
16✔
44
        }
45
    }
46

47
    public function getStatusCode(): int
48
    {
49
        return $this->response->getStatusCode();
12✔
50
    }
51

52
    public function getHeaders(bool $throw = true): array
53
    {
54
        return $this->response->getHeaders($throw);
8✔
55
    }
56

57
    public function getContent(bool $throw = true): string
58
    {
59
        try {
60
            return $this->response->getContent($throw);
12✔
61
        } finally {
62
            $this->endSpan();
12✔
63
        }
64
    }
65

66
    /**
67
     * @return array<mixed>
68
     */
69
    public function toArray(bool $throw = true): array
70
    {
71
        try {
72
            return $this->response->toArray($throw);
×
73
        } finally {
74
            $this->endSpan();
×
75
        }
76
    }
77

78
    public function cancel(): void
79
    {
80
        $this->response->cancel();
×
81
        $this->endSpan();
×
82
    }
83

84
    public function getInfo(?string $type = null): mixed
85
    {
86
        return $this->response->getInfo($type);
×
87
    }
88

89
    public function toStream(bool $throw = true)
90
    {
91
        if ($throw) {
×
92
            $this->response->getHeaders();
×
93
        }
94

95
        if ($this->response instanceof StreamableInterface) {
×
96
            return $this->response->toStream(false);
×
97
        }
98

99
        return StreamWrapper::createResource($this->response, $this->client);
×
100
    }
101

102
    /**
103
     * @internal
104
     *
105
     * @param iterable<TraceableResponse|ResponseInterface> $responses
106
     *
107
     * @return \Generator<TraceableResponse, ChunkInterface>
108
     */
109
    public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator
110
    {
111
        /** @var \SplObjectStorage<ResponseInterface, TraceableResponse> $traceableMap */
112
        $traceableMap = new \SplObjectStorage();
4✔
113
        $wrappedResponses = [];
4✔
114

115
        foreach ($responses as $response) {
4✔
116
            if (!$response instanceof self) {
4✔
117
                throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($response)));
×
118
            }
119

120
            $traceableMap[$response->response] = $response;
4✔
121
            $wrappedResponses[] = $response->response;
4✔
122
        }
123

124
        foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
4✔
125
            $traceableResponse = $traceableMap[$response];
4✔
126
            $traceableResponse->endSpan();
4✔
127

128
            yield $traceableResponse => $chunk;
4✔
129
        }
130
    }
131

132
    private function endSpan(): void
133
    {
134
        if (null === $this->span) {
16✔
135
            return;
×
136
        }
137

138
        $statusCode = $this->response->getStatusCode();
16✔
139
        if (0 !== $statusCode && $this->span->isRecording()) {
16✔
140
            $headers = $this->response->getHeaders(false);
16✔
141
            if (isset($headers['Content-Length'])) {
16✔
NEW
142
                $this->span->setAttribute(HttpIncubatingAttributes::HTTP_RESPONSE_BODY_SIZE, $headers['Content-Length']);
×
143
            }
144

145
            $this->span->setAttribute(HttpAttributes::HTTP_RESPONSE_STATUS_CODE, $statusCode);
16✔
146

147
            if ($statusCode >= 400 && $statusCode < 600) {
16✔
148
                $this->span->setAttribute(HttpAttributes::HTTP_RESPONSE_STATUS_CODE, $statusCode);
4✔
149
                $this->span->setStatus(StatusCode::STATUS_ERROR);
4✔
150
            }
151
        }
152

153
        $this->logger?->debug(sprintf('Ending span "%s"', $this->span->getContext()->getSpanId()));
16✔
154
        $this->span->end();
16✔
155
    }
156
}
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

© 2026 Coveralls, Inc