Passed
Pull Request — master (#19)
by
unknown
04:23 queued 01:28
created

SentryTraceConsoleListener::listenBeginCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 0
cts 4
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
     */
39
    protected ?Span $bootSpan = null;
40
    /**
41
     * The timestamp of application bootstrap completion.
42
     *
43
     */
44
    private ?float $bootedTimestamp;
45
46
    public function __construct(
47
        private HubInterface $hub,
48
    ) {
49
        $this->bootedTimestamp = microtime(true);
50
    }
51
52
    public function getTransaction(): ?Transaction
53
    {
54
        return $this->transaction;
55
    }
56
57
    public function setTransaction(?Transaction $transaction): void
58
    {
59
        $this->transaction = $transaction;
60
    }
61
62
    public function listenAppStart(): void
63
    {
64
        $this->startTransaction();
65
    }
66
67
    private function startTransaction(): void
68
    {
69
        $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...
70
        $context = new TransactionContext();
71
        $context->setOp('console');
72
        $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

72
        $context->setStartTimestamp(/** @scrutinizer ignore-type */ $requestStartTime);
Loading history...
73
        $this->transaction = $this->hub->startTransaction($context);
74
        $this->transaction->setName('undefined command');
75
        SentrySdk::getCurrentHub()->setSpan($this->transaction);
76
        $this->addAppBootstrapSpan();
77
    }
78
79
    private function addAppBootstrapSpan(): void
80
    {
81
        if ($this->bootedTimestamp === null || $this->transaction === null) {
82
            return;
83
        }
84
        $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...
85
86
        $spanContextStart = new SpanContext();
87
        $spanContextStart->setOp('app.bootstrap');
88
        $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

88
        $spanContextStart->setStartTimestamp(/** @scrutinizer ignore-type */ $appStartTime);
Loading history...
89
        $spanContextStart->setEndTimestamp($this->bootedTimestamp);
90
91
        $span = $this->transaction->startChild($spanContextStart);
92
93
        // Consume the booted timestamp, because we don't want to report the bootstrap span more than once
94
        $this->bootedTimestamp = null;
95
96
        // Add more information about the bootstrap section if possible
97
        $this->addBootDetailTimeSpans($span);
98
99
        $this->bootSpan = $span;
100
    }
101
102
    private function addBootDetailTimeSpans(Span $bootstrap): void
103
    {
104
        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...
105
            return;
106
        }
107
108
        $autoload = new SpanContext();
109
        $autoload->setOp('autoload');
110
        $autoload->setStartTimestamp($bootstrap->getStartTimestamp());
111
        $autoload->setEndTimestamp((float)SENTRY_AUTOLOAD);
112
113
        $bootstrap->startChild($autoload);
114
    }
115
116
    public function listenBeginCommand(ConsoleCommandEvent $event): void
117
    {
118
        $command = $event->getCommand();
119
        $input = $event->getInput();
120
        $this->startCommand($command, $input);
121
    }
122
123
    private function startCommand(?Command $command, InputInterface $input): void
124
    {
125
        if ($this->transaction === null) {
126
            return;
127
        }
128
        $name = $command?->getName() ?? 'undefined command';
129
        $inputArgs = [
130
            'arguments' => $input->getArguments(),
131
            'options' => $input->getOptions(),
132
        ];
133
        $this->transaction->setData(
134
            [
135
            'name' => $name,
136
            'input' => $inputArgs,
137
            ]
138
        );
139
        $this->transaction->setName($name);
140
141
        $bootstrapSpan = $this->bootSpan;
142
143
        $appContextStart = new SpanContext();
144
        $appContextStart->setOp('app.handle');
145
        $startTimestamp = $bootstrapSpan ? $bootstrapSpan->getEndTimestamp() : microtime(true);
146
        $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

146
        $appContextStart->setStartTimestamp(/** @scrutinizer ignore-type */ $startTimestamp);
Loading history...
147
148
        $this->appSpan = $this->transaction->startChild($appContextStart);
149
150
        SentrySdk::getCurrentHub()->setSpan($this->appSpan);
151
    }
152
153
    public function listenShutdown(ApplicationShutdown $event): void
154
    {
155
        $exitCode = $event->getExitCode();
156
        $this->terminate($exitCode);
157
    }
158
159
    public function terminate(int $exitCode): void
160
    {
161
        if ($this->transaction !== null) {
162
            $this->appSpan?->finish();
163
            $this->appSpan = null;
164
165
            $this->transaction->setTags(['exitCode' => (string)$exitCode]);
166
            // Make sure we set the transaction and not have a child span in the Sentry SDK
167
            // If the transaction is not on the scope during finish, the `trace.context` is wrong
168
            SentrySdk::getCurrentHub()->setSpan($this->transaction);
169
170
            $this->transaction->finish();
171
            $this->transaction = null;
172
        }
173
    }
174
175
    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

175
    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...
176
    {
177
        $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

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