NewRelicAgent::isTransactionEnded()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
declare(strict_types=1);
3
namespace ZERO2TEN\Observability\APM\Agent\NewRelic;
4
5
use InvalidArgumentException;
6
use Throwable;
7
use ZERO2TEN\Observability\APM\Agent\Agent;
8
use ZERO2TEN\Observability\APM\Datastore;
9
use ZERO2TEN\Observability\APM\NativeExtensions;
10
use ZERO2TEN\Observability\APM\TransactionInterface;
11
12
use function array_filter;
13
use function gettype;
14
15
/**
16
 * NewRelicAgent
17
 *
18
 * Implementation of the AgentInterface for working with the PHP extension provided by New Relic.
19
 *
20
 * @method bool newrelic_add_custom_tracer(string $functionName)
21
 * @method void newrelic_capture_params(bool $enableFlag = true)
22
 * @method void newrelic_record_custom_event(string $name, array $attributes)
23
 * @method bool newrelic_custom_metric(string $name, float $value)
24
 * @method bool newrelic_set_appname(string $name, string $license, bool $xmit = false)
25
 *
26
 * -- Transaction --
27
 * @method bool newrelic_add_custom_parameter(string $key, bool|float|int|string $value)
28
 * @method void newrelic_background_job(bool $flag = true)
29
 * @method void newrelic_end_of_transaction()
30
 * @method bool newrelic_end_transaction(bool $ignore = false)
31
 * @method void newrelic_ignore_apdex()
32
 * @method void newrelic_ignore_transaction()
33
 * @method bool newrelic_is_sampled()
34
 * @method bool newrelic_name_transaction(string $name)
35
 * @method void newrelic_notice_error(string $message, Throwable $e)
36
 * @method bool newrelic_start_transaction(string $applicationName, string $license = null)
37
 * @method mixed newrelic_record_datastore_segment(callable $function, array $parameters)
38
 * @method bool newrelic_set_user_attributes(string $user, string $account, string $product)
39
 *
40
 * -- Browser --
41
 * @method bool|null newrelic_disable_autorum()
42
 * @method string newrelic_get_browser_timing_header(bool $includeTags = true)
43
 * @method string newrelic_get_browser_timing_footer(bool $includeTags = true)
44
 *
45
 * -- Distributed tracing --
46
 * @method bool newrelic_accept_distributed_trace_headers(array $headers, string $transport_type = 'HTTP')
47
 * @method bool newrelic_add_custom_span_parameter(string $key, bool|float|int|string $value)
48
 * @method array newrelic_get_linking_metadata()
49
 * @method array newrelic_get_trace_metadata()
50
 * @method bool newrelic_insert_distributed_trace_headers(array $headers)
51
 *
52
 * @copyright Copyright (c) 2023 0TO10 B.V. <https://0to10.nl>
53
 * @package ZERO2TEN\Observability\APM\Agent\NewRelic
54
 */
55
class NewRelicAgent extends Agent
56
{
57
    use NativeExtensions;
58
59
    private const EXTENSION_NAME = 'newrelic';
60
    private const EXTENSION_INI_APP_NAME = self::EXTENSION_NAME . '.appname';
61
    private const EXTENSION_INI_LICENSE = self::EXTENSION_NAME . '.license';
62
63
    private const IGNORE_TRANSACTION_ON_END = false;
64
65
    private const RESERVED_WORDS = [
66
        'accountId',
67
        'appId',
68
        'timestamp',
69
        'type',
70
        'eventType',
71
    ];
72
73
    /** @var string */
74
    private $applicationName = '';
75
    /** @var string */
76
    private $license = '';
77
    /** @var bool */
78
    private $transactionEnded = false;
79
80
    /**
81
     * @inheritDoc
82
     */
83 5
    protected function initialise(): bool
84
    {
85 5
        if (!$this->isExtensionLoaded(self::EXTENSION_NAME)) {
86 5
            return false;
87
        }
88
89 5
        $this->reserveWords(...self::RESERVED_WORDS);
90
91 5
        $this->applicationName = $this->getConfigurationOption(self::EXTENSION_INI_APP_NAME);
92 5
        $this->license = $this->getConfigurationOption(self::EXTENSION_INI_LICENSE);
93
94 5
        return !empty($this->applicationName) && !empty($this->license);
95
    }
96
97
    /**
98
     * @inheritdoc
99
     */
100 3
    public function changeApplicationName(string $name, bool $ignoreTransaction = true): void
101
    {
102 3
        $this->newrelic_set_appname($name, $this->license, !$ignoreTransaction);
103
    }
104
105
    /**
106
     * @inheritDoc
107
     */
108 3
    public function captureUrlParameters(bool $enable = true): void
109
    {
110 3
        $this->newrelic_capture_params($enable);
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116 6
    public function recordCustomEvent(string $name, array $attributes): void
117
    {
118 6
        $this->guardIsNotReservedWord($name);
119
120 1
        $this->newrelic_record_custom_event($name, $attributes);
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 6
    public function addCustomMetric(string $name, float $milliseconds): void
127
    {
128 6
        $this->guardIsNotReservedWord($name);
129
130 1
        $this->newrelic_custom_metric('Custom/' . $name, $milliseconds);
131
    }
132
133
    /**
134
     * @inheritdoc
135
     */
136 1
    public function disableAutomaticBrowserMonitoringScripts(): void
137
    {
138 1
        $this->newrelic_disable_autorum();
139
    }
140
141
    /**
142
     * @inheritdoc
143
     */
144 1
    public function getBrowserMonitoringHeaderScript(): string
145
    {
146 1
        return $this->newrelic_get_browser_timing_header(false);
147
    }
148
149
    /**
150
     * @inheritdoc
151
     */
152 1
    public function getBrowserMonitoringFooterScript(): string
153
    {
154 1
        return $this->newrelic_get_browser_timing_footer(false);
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160 3
    public function startTransaction(bool $ignorePrevious = false): TransactionInterface
161
    {
162 3
        if ($ignorePrevious) {
163 1
            $this->logger->info('Ignoring the previous transaction does nothing. Use the `Transaction` class instead.');
0 ignored issues
show
Bug introduced by
The method info() does not exist on null. ( Ignorable by Annotation )

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

163
            $this->logger->/** @scrutinizer ignore-call */ 
164
                           info('Ignoring the previous transaction does nothing. Use the `Transaction` class instead.');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
164
        }
165
166 3
        $this->transactionEnded = !$this->newrelic_start_transaction($this->applicationName, $this->license);
167
168 3
        return $this->createTransaction($this);
169
    }
170
171
    /**
172
     * @inheritdoc
173
     */
174 2
    public function changeTransactionName(string $name): void
175
    {
176 2
        if (!$this->newrelic_name_transaction($name)) {
177 1
            $this->logger->info('[APM] Unable to change current transaction name.');
178
        }
179
    }
180
181
    /**
182
     * @inheritdoc
183
     */
184 8
    public function addTransactionParameter(string $name, $value): void
185
    {
186 8
        $this->guardIsNotReservedWord($name);
187
188 7
        if (null !== $value && !is_scalar($value)) {
189 3
            throw new InvalidArgumentException('Transaction parameter value must be scalar, "' . gettype($value) . '" given.');
190
        }
191
192 4
        $this->newrelic_add_custom_parameter($name, $value);
193
    }
194
195
    /**
196
     * @inheritdoc
197
     */
198 2
    public function markTransactionAsBackground(bool $background): void
199
    {
200 2
        $this->newrelic_background_job($background);
201
    }
202
203
    /**
204
     * @inheritdoc
205
     */
206 1
    public function recordTransactionException(string $message, Throwable $e): void
207
    {
208 1
        $this->newrelic_notice_error($message, $e);
209
    }
210
211
    /**
212
     * @inheritDoc
213
     */
214 6
    public function addTransactionDatastoreSegment(
215
        Datastore $datastore,
216
        callable $callable,
217
        string $query = null,
218
        string $inputQueryLabel = null,
219
        string $inputQuery = null
220
    )
221
    {
222 6
        $parameters = [
223 6
            'product' => $datastore->product(),
224 6
            'collection' => $datastore->collection(),
225 6
            'operation' => $datastore->operation(),
226 6
            'host' => $datastore->host(),
227 6
            'portPathOrId' => null,
228 6
            'databaseName' => $datastore->database(),
229 6
            'query' => $query,
230 6
            'inputQueryLabel' => $inputQueryLabel,
231 6
            'inputQuery' => $inputQuery,
232 6
        ];
233
234 6
        $parameters = array_filter($parameters, static function ($value): bool {
235 6
            return null !== $value;
236 6
        });
237
238 6
        return $this->newrelic_record_datastore_segment($callable, $parameters);
239
    }
240
241
    /**
242
     * @inheritdoc
243
     */
244 1
    public function stopTransactionTiming(): void
245
    {
246 1
        $this->newrelic_end_of_transaction();
247
    }
248
249
    /**
250
     * @inheritdoc
251
     */
252 1
    public function ignoreTransactionApdex(): void
253
    {
254 1
        $this->newrelic_ignore_apdex();
255
    }
256
257
    /**
258
     * @inheritdoc
259
     */
260 1
    public function ignoreTransaction(): void
261
    {
262 1
        $this->newrelic_ignore_transaction();
263
    }
264
265
    /**
266
     * @inheritDoc
267
     */
268 2
    public function isTransactionSampled(): bool
269
    {
270 2
        return $this->newrelic_is_sampled();
271
    }
272
273
    /**
274
     * @inheritdoc
275
     */
276 1
    public function endTransaction(): void
277
    {
278 1
        if ($this->transactionEnded) {
279 1
            return;
280
        }
281
282 1
        $this->transactionEnded = $this->newrelic_end_transaction(self::IGNORE_TRANSACTION_ON_END);
283
    }
284
285
    /**
286
     * @inheritDoc
287
     */
288 1
    public function isTransactionEnded(): bool
289
    {
290 1
        return $this->transactionEnded;
291
    }
292
}
293