ErrorMiddleware::createStackTrace()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 18
c 0
b 0
f 0
ccs 11
cts 11
cp 1
rs 9.6666
cc 1
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace TechDeCo\ElasticApmAgent\Convenience\Middleware;
5
6
use Psr\Http\Message\ResponseInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Server\RequestHandlerInterface;
9
use Ramsey\Uuid\Uuid;
10
use TechDeCo\ElasticApmAgent\Client;
11
use TechDeCo\ElasticApmAgent\Convenience\OpenTransaction;
12
use TechDeCo\ElasticApmAgent\Exception\ClientException;
13
use TechDeCo\ElasticApmAgent\Message\Context;
14
use TechDeCo\ElasticApmAgent\Message\Error as ErrorMessage;
15
use TechDeCo\ElasticApmAgent\Message\Exception;
16
use TechDeCo\ElasticApmAgent\Message\Process;
17
use TechDeCo\ElasticApmAgent\Message\Request;
18
use TechDeCo\ElasticApmAgent\Message\Service;
19
use TechDeCo\ElasticApmAgent\Message\StackTraceFrame;
20
use TechDeCo\ElasticApmAgent\Message\System;
21
use TechDeCo\ElasticApmAgent\Message\Timestamp;
22
use TechDeCo\ElasticApmAgent\Message\Url;
23
use TechDeCo\ElasticApmAgent\Request\Error as ErrorRequest;
24
use Throwable;
25
use function array_filter;
26
use function array_map;
27
use function get_class;
28
use function implode;
29
30
final class ErrorMiddleware extends Middleware
31
{
32
    /**
33
     * @var Context
34
     */
35
    private $context;
36
37 14
    public function __construct(
38
        Client $client,
39
        Service $service,
40
        Process $process,
41
        System $system,
42
        ?Context $context = null
43
    ) {
44 14
        parent::__construct($client, $service, $process, $system);
45
46 14
        $this->context = $context ?? new Context();
47 14
    }
48
49
    /**
50
     * @throws ClientException
51
     * @throws Throwable
52
     */
53 14
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
54
    {
55
        try {
56 14
            return $handler->handle($request);
57 13
        } catch (Throwable $t) {
58 13
            $message = $this->createMessage($request, $t);
59 13
            $request = (new ErrorRequest($this->service, $message))
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $request. This often makes code more readable.
Loading history...
60 13
                ->onSystem($this->system)
61 13
                ->inProcess($this->process);
62
63 13
            $this->client->sendError($request);
64
65 13
            throw $t;
66
        }
67
    }
68
69 13
    private function createMessage(ServerRequestInterface $request, Throwable $throwable): ErrorMessage
70
    {
71 13
        $message = ErrorMessage::fromException($this->createException($throwable), new Timestamp())
72 13
                               ->withId(Uuid::uuid4())
73 13
                               ->inContext($this->createContext($request));
74
75
        /** @var OpenTransaction|null $transaction */
76 13
        $transaction = $request->getAttribute(TransactionMiddleware::TRANSACTION_ATTRIBUTE);
77 13
        if ($transaction !== null) {
78 13
            $message = $message->correlatedToTransactionId($transaction->getId());
79
        }
80
81 13
        return $message;
82
    }
83
84 13
    private function createException(Throwable $throwable): Exception
85
    {
86 13
        return (new Exception($throwable->getMessage()))
87 13
            ->withCode($throwable->getCode())
88 13
            ->withStackTraceFrame(...$this->createStackTrace($throwable))
89 13
            ->asType(get_class($throwable));
90
    }
91
92
    /**
93
     * @return StackTraceFrame[]
94
     */
95 13
    private function createStackTrace(Throwable $throwable): array
96
    {
97 13
        return array_map(
98
            function (array $frame): StackTraceFrame {
99 13
                $function = implode('::', array_filter([
100 13
                    $frame['class'] ?? null,
101 13
                    $frame['function'] ?? null,
102
                ]));
103
104 13
                return (new StackTraceFrame(
105 13
                    $frame['file'] ?? '<undefined>',
106 13
                    $frame['line'] ?? 0
107
                ))
108 13
                    ->inFunction($function);
109 13
            },
110 13
            $throwable->getTrace()
111
        );
112
    }
113
114 13
    private function createContext(ServerRequestInterface $request): Context
115
    {
116 13
        $request = (new Request(
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $request. This often makes code more readable.
Loading history...
117 13
            $request->getMethod(),
118 13
            Url::fromUri($request->getUri())
119
        ))
120 13
            ->onHttpVersion($request->getProtocolVersion());
121
122 13
        return $this->context->withRequest($request);
123
    }
124
}
125