Passed
Pull Request — master (#19)
by
unknown
02:50
created

SentryTraceConsoleListener::listenAppStart()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Sentry\Tracing;
6
7
use Sentry\SentrySdk;
8
use Sentry\State\HubInterface;
9
use Sentry\Tracing\Span;
10
use Sentry\Tracing\SpanContext;
11
use Sentry\Tracing\Transaction;
12
use Sentry\Tracing\TransactionContext;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Event\ConsoleCommandEvent;
15
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Yiisoft\Yii\Console\Event\ApplicationShutdown;
18
19
final class SentryTraceConsoleListener
20
{
21
    /**
22
     * The current active transaction.
23
     *
24
     * @psalm-suppress PropertyNotSetInConstructor
25
     */
26
    protected ?Transaction $transaction = null;
27
    /**
28
     * The span for the `app.handle` part of the application.
29
     *
30
     * @psalm-suppress PropertyNotSetInConstructor
31
     */
32
    protected ?Span $appSpan = null;
33
    /**
34
     * The span for the `app.handle` part of the application.
35
     *
36
     * @psalm-suppress PropertyNotSetInConstructor
37
     */
38
    protected ?Span $bootSpan = null;
39
    /**
40
     * The timestamp of application bootstrap completion.
41
     */
42
    private ?float $bootedTimestamp;
43
44
    public function __construct(
45
        private HubInterface $hub,
46
    ) {
47
        $this->bootedTimestamp = microtime(true);
48
    }
49
50
    public function getTransaction(): ?Transaction
51
    {
52
        return $this->transaction;
53
    }
54
55
    public function setTransaction(?Transaction $transaction): void
56
    {
57
        $this->transaction = $transaction;
58
    }
59
60
    public function listenAppStart(): void
61
    {
62
        $this->startTransaction();
63
    }
64
65
    private function startTransaction(): void
66
    {
67
        $requestStartTime = defined('APP_START_TIME') ? (float)APP_START_TIME : microtime(true);
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Sentry\Tracing\APP_START_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
68
        $context = new TransactionContext();
69
        $context->setOp('console');
70
        $context->setStartTimestamp($requestStartTime);
0 ignored issues
show
Bug introduced by
It seems like $requestStartTime can also be of type string; however, parameter $startTimestamp of Sentry\Tracing\SpanContext::setStartTimestamp() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
        $context->setStartTimestamp(/** @scrutinizer ignore-type */ $requestStartTime);
Loading history...
71
        $this->transaction = $this->hub->startTransaction($context);
72
        $this->transaction->setName('undefined command');
73
        SentrySdk::getCurrentHub()->setSpan($this->transaction);
74
        $this->addAppBootstrapSpan();
75
    }
76
77
    private function addAppBootstrapSpan(): void
78
    {
79
        if ($this->bootedTimestamp === null || $this->transaction === null) {
80
            return;
81
        }
82
        $appStartTime = defined('APP_START_TIME') ? (float)APP_START_TIME : microtime(true);
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Sentry\Tracing\APP_START_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
83
84
        $spanContextStart = new SpanContext();
85
        $spanContextStart->setOp('app.bootstrap');
86
        $spanContextStart->setStartTimestamp($appStartTime);
0 ignored issues
show
Bug introduced by
It seems like $appStartTime can also be of type string; however, parameter $startTimestamp of Sentry\Tracing\SpanContext::setStartTimestamp() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        $spanContextStart->setStartTimestamp(/** @scrutinizer ignore-type */ $appStartTime);
Loading history...
87
        $spanContextStart->setEndTimestamp($this->bootedTimestamp);
88
89
        $span = $this->transaction->startChild($spanContextStart);
90
91
        // Consume the booted timestamp, because we don't want to report the bootstrap span more than once
92
        $this->bootedTimestamp = null;
93
94
        // Add more information about the bootstrap section if possible
95
        $this->addBootDetailTimeSpans($span);
96
97
        $this->bootSpan = $span;
98
    }
99
100
    private function addBootDetailTimeSpans(Span $bootstrap): void
101
    {
102
        if (!defined('SENTRY_AUTOLOAD') || !SENTRY_AUTOLOAD || !is_numeric(SENTRY_AUTOLOAD)) {
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Sentry\Tracing\SENTRY_AUTOLOAD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
103
            return;
104
        }
105
106
        $autoload = new SpanContext();
107
        $autoload->setOp('autoload');
108
        $autoload->setStartTimestamp($bootstrap->getStartTimestamp());
109
        $autoload->setEndTimestamp((float)SENTRY_AUTOLOAD);
110
111
        $bootstrap->startChild($autoload);
112
    }
113
114
    public function listenBeginCommand(ConsoleCommandEvent $event): void
115
    {
116
        $command = $event->getCommand();
117
        $input = $event->getInput();
118
        $this->startCommand($command, $input);
119
    }
120
121
    private function startCommand(?Command $command, InputInterface $input): void
122
    {
123
        if ($this->transaction === null) {
124
            return;
125
        }
126
        $name = $command?->getName() ?? 'undefined command';
127
        $inputArgs = [
128
            'arguments' => $input->getArguments(),
129
            'options' => $input->getOptions(),
130
        ];
131
        $this->transaction->setData(
132
            [
133
                'name' => $name,
134
                'input' => $inputArgs,
135
            ]
136
        );
137
        $this->transaction->setName($name);
138
139
        $bootstrapSpan = $this->bootSpan;
140
141
        $appContextStart = new SpanContext();
142
        $appContextStart->setOp('app.handle');
143
        $startTimestamp = $bootstrapSpan ? $bootstrapSpan->getEndTimestamp() : microtime(true);
144
        $appContextStart->setStartTimestamp($startTimestamp);
0 ignored issues
show
Bug introduced by
It seems like $startTimestamp can also be of type string; however, parameter $startTimestamp of Sentry\Tracing\SpanContext::setStartTimestamp() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
        $appContextStart->setStartTimestamp(/** @scrutinizer ignore-type */ $startTimestamp);
Loading history...
145
146
        $this->appSpan = $this->transaction->startChild($appContextStart);
147
148
        SentrySdk::getCurrentHub()->setSpan($this->appSpan);
149
    }
150
151
    public function listenShutdown(ApplicationShutdown $event): void
152
    {
153
        $exitCode = $event->getExitCode();
154
        $this->terminate($exitCode);
155
    }
156
157
    public function terminate(int $exitCode): void
158
    {
159
        if ($this->transaction !== null) {
160
            $this->appSpan?->finish();
161
            $this->appSpan = null;
162
163
            $this->transaction->setTags(['exitCode' => (string)$exitCode]);
164
            // Make sure we set the transaction and not have a child span in the Sentry SDK
165
            // If the transaction is not on the scope during finish, the `trace.context` is wrong
166
            SentrySdk::getCurrentHub()->setSpan($this->transaction);
167
168
            $this->transaction->finish();
169
            $this->transaction = null;
170
        }
171
    }
172
173
    public function listenCommandTerminate(?ConsoleTerminateEvent $terminateEvent): void
0 ignored issues
show
Unused Code introduced by
The parameter $terminateEvent is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

173
    public function listenCommandTerminate(/** @scrutinizer ignore-unused */ ?ConsoleTerminateEvent $terminateEvent): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
174
    {
175
        $this->appSpan?->finish(microtime(true));
0 ignored issues
show
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $endTimestamp of Sentry\Tracing\Span::finish() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
        $this->appSpan?->finish(/** @scrutinizer ignore-type */ microtime(true));
Loading history...
176
    }
177
}
178