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

FriendsOfOpenTelemetry / opentelemetry-bundle / 7752972926

02 Feb 2024 07:53AM UTC coverage: 35.071% (-29.5%) from 64.527%
7752972926

Pull #38

github

gaelreyrol
wip
Pull Request #38: Refactor services injection

99 of 459 new or added lines in 44 files covered. (21.57%)

314 existing lines in 33 files now uncovered.

740 of 2110 relevant lines covered (35.07%)

2.37 hits per line

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

0.0
/src/Instrumentation/Symfony/HttpKernel/TraceableHttpKernelEventSubscriber.php
1
<?php
2

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

5
use FriendsOfOpenTelemetry\OpenTelemetryBundle\OpenTelemetry\Context\Attribute\HttpKernelTraceAttributeEnum;
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\Propagation\PropagationGetterInterface;
12
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
13
use OpenTelemetry\Context\ScopeInterface;
14
use OpenTelemetry\SemConv\TraceAttributes;
15
use Psr\Log\LoggerInterface;
16
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17
use Symfony\Component\HttpFoundation\HeaderBag;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
20
use Symfony\Component\HttpKernel\Event\ControllerEvent;
21
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
22
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
23
use Symfony\Component\HttpKernel\Event\RequestEvent;
24
use Symfony\Component\HttpKernel\Event\ResponseEvent;
25
use Symfony\Component\HttpKernel\Event\TerminateEvent;
26
use Symfony\Component\HttpKernel\Event\ViewEvent;
27
use Symfony\Component\HttpKernel\KernelEvents;
28

29
final class TraceableHttpKernelEventSubscriber implements EventSubscriberInterface
30
{
31
    private const REQUEST_ATTRIBUTE_SPAN = '__opentelemetry_symfony_internal_span';
32
    private const REQUEST_ATTRIBUTE_SCOPE = '__opentelemetry_symfony_internal_scope';
33
    private const REQUEST_ATTRIBUTE_EXCEPTION = '__opentelemetry_symfony_internal_exception';
34

35
    /**
36
     * @var array<string, ?string>
37
     */
38
    private array $requestHeaderAttributes;
39

40
    /**
41
     * @var array<string, ?string>
42
     */
43
    private array $responseHeaderAttributes;
44

45
    /**
46
     * @param iterable<string> $requestHeaders
47
     * @param iterable<string> $responseHeaders
48
     */
49
    public function __construct(
50
        private readonly TracerInterface $tracer,
51
        private readonly TextMapPropagatorInterface $propagator,
52
        private readonly PropagationGetterInterface $propagationGetter,
53
        /** @phpstan-ignore-next-line  */
54
        private readonly LoggerInterface $logger,
55
        iterable $requestHeaders = [],
56
        iterable $responseHeaders = [],
57
    ) {
58
        $this->requestHeaderAttributes = $this->createHeaderAttributeMapping('request', $requestHeaders);
×
59
        $this->responseHeaderAttributes = $this->createHeaderAttributeMapping('response', $responseHeaders);
×
60
    }
61

62
    public static function getSubscribedEvents(): array
63
    {
64
        return [
×
65
            KernelEvents::REQUEST => [
×
66
                ['startRequest', 10000],
×
67
                ['recordRoute', 31], // after RouterListener
×
68
            ],
×
69
            KernelEvents::CONTROLLER => [
×
70
                ['recordController'],
×
71
            ],
×
72
            KernelEvents::CONTROLLER_ARGUMENTS => [
×
73
                ['recordControllerArguments'],
×
74
            ],
×
75
            KernelEvents::VIEW => [
×
76
                ['recordView'],
×
77
            ],
×
78
            KernelEvents::RESPONSE => [
×
79
                ['recordResponse', -10000],
×
80
            ],
×
81
            KernelEvents::FINISH_REQUEST => [
×
82
                ['endScope', -10000],
×
83
                ['endRequest', -10000],
×
84
            ],
×
85
            KernelEvents::TERMINATE => [
×
86
                ['terminateRequest', 10000],
×
87
            ],
×
88
            KernelEvents::EXCEPTION => [
×
89
                ['recordException'],
×
90
            ],
×
91
        ];
×
92
    }
93

94
    public function startRequest(RequestEvent $event): void
95
    {
96
        $request = $event->getRequest();
×
97

98
        $spanBuilder = $this->tracer
×
99
            ->spanBuilder(sprintf('HTTP %s', $request->getMethod()))
×
100
            ->setAttributes($this->requestAttributes($request))
×
101
            ->setAttributes($this->headerAttributes($request->headers, $this->requestHeaderAttributes))
×
102
        ;
×
103

104
        $parent = Context::getCurrent();
×
105

106
        if ($event->isMainRequest()) {
×
107
            $spanBuilder->setSpanKind(SpanKind::KIND_SERVER);
×
108
            $parent = $this->propagator->extract(
×
109
                $request,
×
110
                $this->propagationGetter,
×
111
                $parent,
×
112
            );
×
113

114
            $requestTime = $request->server->get('REQUEST_TIME_FLOAT');
×
115
            if (null !== $requestTime) {
×
116
                $spanBuilder->setStartTimestamp($requestTime * 1_000_000_000);
×
117
            }
118
        }
119

120
        $span = $spanBuilder->setParent($parent)->startSpan();
×
121
        $scope = $span->storeInContext($parent)->activate();
×
122

123
        $request->attributes->set(self::REQUEST_ATTRIBUTE_SPAN, $span);
×
124
        $request->attributes->set(self::REQUEST_ATTRIBUTE_SCOPE, $scope);
×
125
    }
126

127
    public function recordRoute(RequestEvent $event): void
128
    {
129
        $span = $this->fetchRequestSpan($event->getRequest());
×
130
        if (null === $span) {
×
131
            return;
×
132
        }
133

134
        $routeName = $event->getRequest()->attributes->get('_route', '');
×
135
        if ('' === $routeName) {
×
136
            return;
×
137
        }
138

139
        $span->updateName($routeName);
×
140
        $span->setAttribute(TraceAttributes::HTTP_ROUTE, $routeName);
×
141
    }
142

143
    public function recordController(ControllerEvent $event): void
144
    {
145
        $span = $this->fetchRequestSpan($event->getRequest());
×
146
        if (null === $span) {
×
147
            return;
×
148
        }
149
    }
150

151
    public function recordControllerArguments(ControllerArgumentsEvent $event): void
152
    {
153
        $span = $this->fetchRequestSpan($event->getRequest());
×
154
        if (null === $span) {
×
155
            return;
×
156
        }
157
    }
158

159
    public function recordView(ViewEvent $event): void
160
    {
161
        $span = $this->fetchRequestSpan($event->getRequest());
×
162
        if (null === $span) {
×
163
            return;
×
164
        }
165
    }
166

167
    public function recordException(ExceptionEvent $event): void
168
    {
169
        $span = $this->fetchRequestSpan($event->getRequest());
×
170
        if (null === $span) {
×
171
            return;
×
172
        }
173

174
        $span->recordException($event->getThrowable());
×
175
        $event->getRequest()->attributes->set(self::REQUEST_ATTRIBUTE_EXCEPTION, $event->getThrowable());
×
176
    }
177

178
    public function recordResponse(ResponseEvent $event): void
179
    {
180
        $span = $this->fetchRequestSpan($event->getRequest());
×
181
        if (null === $span) {
×
182
            return;
×
183
        }
184

185
        $event->getRequest()->attributes->remove(self::REQUEST_ATTRIBUTE_EXCEPTION);
×
186

187
        if (!$span->isRecording()) {
×
188
            return;
×
189
        }
190

191
        $response = $event->getResponse();
×
192
        $span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->headers->get('Content-Length'));
×
193
        $span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
×
194
        $span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());
×
195
        if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) {
×
196
            $span->setStatus(StatusCode::STATUS_ERROR);
×
197
        } else {
198
            $span->setStatus(StatusCode::STATUS_OK);
×
199
        }
200

201
        $span->setAttributes($this->headerAttributes($response->headers, $this->responseHeaderAttributes));
×
202
    }
203

204
    public function endScope(FinishRequestEvent $event): void
205
    {
206
        $scope = $this->fetchRequestScope($event->getRequest());
×
207
        if (null === $scope) {
×
208
            return;
×
209
        }
210
        $scope->detach();
×
211
    }
212

213
    public function endRequest(FinishRequestEvent $event): void
214
    {
215
        $span = $this->fetchRequestSpan($event->getRequest());
×
216
        if (null === $span) {
×
217
            return;
×
218
        }
219

220
        $exception = $this->fetchRequestException($event->getRequest());
×
221
        if (null !== $exception) {
×
222
            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
×
223
        } elseif ($event->isMainRequest()) {
×
224
            // End span on ::terminateRequest() instead
225
            return;
×
226
        }
227

228
        $span->end();
×
229
    }
230

231
    public function terminateRequest(TerminateEvent $event): void
232
    {
233
        $span = $this->fetchRequestSpan($event->getRequest());
×
234
        if (null === $span) {
×
235
            return;
×
236
        }
237

238
        $span->end();
×
239
    }
240

241
    private function fetchRequestSpan(Request $request): ?SpanInterface
242
    {
243
        return $this->fetchRequestAttribute($request, self::REQUEST_ATTRIBUTE_SPAN, SpanInterface::class);
×
244
    }
245

246
    private function fetchRequestScope(Request $request): ?ScopeInterface
247
    {
248
        return $this->fetchRequestAttribute($request, self::REQUEST_ATTRIBUTE_SCOPE, ScopeInterface::class);
×
249
    }
250

251
    private function fetchRequestException(Request $request): ?\Throwable
252
    {
253
        return $this->fetchRequestAttribute($request, self::REQUEST_ATTRIBUTE_EXCEPTION, \Throwable::class);
×
254
    }
255

256
    /**
257
     * @template T of object
258
     *
259
     * @param class-string<T> $type
260
     *
261
     * @phpstan-return T|null
262
     */
263
    private function fetchRequestAttribute(Request $request, string $key, string $type): ?object
264
    {
265
        return ($object = $request->attributes->get($key)) instanceof $type ? $object : null;
×
266
    }
267

268
    /**
269
     * @return iterable<string, string>
270
     */
271
    private function requestAttributes(Request $request): iterable
272
    {
273
        return [
×
274
            TraceAttributes::URL_FULL => $request->getUri(),
×
275
            TraceAttributes::HTTP_REQUEST_METHOD => $request->getMethod(),
×
276
            TraceAttributes::URL_PATH => $request->getPathInfo(),
×
277
            HttpKernelTraceAttributeEnum::HttpHost->toString() => $request->getHttpHost(),
×
278
            TraceAttributes::URL_SCHEME => $request->getScheme(),
×
279
            TraceAttributes::NETWORK_PROTOCOL_VERSION => ($protocolVersion = $request->getProtocolVersion()) !== null
×
280
                ? strtr($protocolVersion, ['HTTP/' => ''])
×
UNCOV
281
                : null,
×
282
            TraceAttributes::USER_AGENT_ORIGINAL => $request->headers->get('User-Agent'),
×
283
            TraceAttributes::HTTP_REQUEST_BODY_SIZE => $request->headers->get('Content-Length'),
×
284
            TraceAttributes::NETWORK_PEER_ADDRESS => $request->getClientIp(),
×
285

286
            HttpKernelTraceAttributeEnum::NetPeerIp->toString() => $request->server->get('REMOTE_ADDR'),
×
287
            TraceAttributes::CLIENT_ADDRESS => $request->server->get('REMOTE_HOST'),
×
288
            TraceAttributes::CLIENT_PORT => $request->server->get('REMOTE_PORT'),
×
289
            HttpKernelTraceAttributeEnum::NetHostIp->toString() => $request->server->get('SERVER_ADDR'),
×
290
            TraceAttributes::SERVER_ADDRESS => $request->server->get('SERVER_NAME'),
×
291
            TraceAttributes::SERVER_PORT => $request->server->get('SERVER_PORT'),
×
292
        ];
×
293
    }
294

295
    /**
296
     * @param array<string> $headers
297
     *
298
     * @return array<string, mixed>
299
     */
300
    private function headerAttributes(HeaderBag $headerBag, array $headers): iterable
301
    {
302
        foreach ($headers as $header => $attribute) {
×
303
            if ($headerBag->has($header)) {
×
304
                yield $attribute => $headerBag->all($header);
×
305
            }
306
        }
307
    }
308

309
    /**
310
     * @param iterable<string> $headers
311
     *
312
     * @return array<string, string>
313
     */
314
    private function createHeaderAttributeMapping(string $type, iterable $headers): array
315
    {
316
        $headerAttributes = [];
×
317
        foreach ($headers as $header) {
×
318
            $lcHeader = strtolower($header);
×
319
            $headerAttributes[$lcHeader] = sprintf('http.%s.header.%s', $type, strtr($lcHeader, ['-' => '_']));
×
320
        }
321

322
        return $headerAttributes;
×
323
    }
324
}
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