Skip to content

Commit 8d470f4

Browse files
authored
Release v3.24.0
2 parents 138d6c3 + 355c7ef commit 8d470f4

22 files changed

Lines changed: 672 additions & 46 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
Changelog
22
=========
33

4+
## 3.24.0 (2020-10-27)
5+
6+
This release changes how Bugsnag detects the error suppression operator in combination with the `errorReportingLevel` configuration option, for PHP 8 compatibility. Bugsnag's `errorReportingLevel` must now be a subset of `error_reporting` — i.e. every error level in `errorReportingLevel` must also be in `error_reporting`
7+
8+
If you use the `errorReportingLevel` option, you may need to change your Bugsnag or PHP configuration in order to report all expected errors. See [PR #611](https://github.com/bugsnag/bugsnag-php/pull/611) for more details
9+
10+
### Enhancements
11+
12+
* Improve the display of breadrumbs in the Bugsnag app by including milliseconds in timestamps
13+
[#612](https://github.com/bugsnag/bugsnag-php/pull/612)
14+
15+
### Fixes
16+
17+
* Make `Configuration::shouldIgnoreErrorCode` compatible with PHP 8 by requiring the `errorReportingLevel` option to be a subset of `error_reporting`
18+
[#611](https://github.com/bugsnag/bugsnag-php/pull/611)
19+
420
## 3.23.1 (2020-10-19)
521

622
This release fixes several issues with Bugsnag's error handlers that caused it to affect the behaviour of shutdown functions ([#475](https://github.com/bugsnag/bugsnag-php/issues/475)) and CLI script exit codes ([#523](https://github.com/bugsnag/bugsnag-php/issues/523)). This does not apply if you are using the Laravel or Symfony integrations, as they use separate methods of error handling.

phpunit.xml.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@
2929
<directory suffix=".phpt">./tests/phpt</directory>
3030
</testsuite>
3131
</testsuites>
32+
33+
<php>
34+
<!-- Use a very big number as we can't use the 'E_ALL' constant here -->
35+
<ini name="error_reporting" value="2147483647" />
36+
</php>
3237
</phpunit>

src/Breadcrumbs/Breadcrumb.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Bugsnag\Breadcrumbs;
44

5+
use Bugsnag\DateTime\Date;
56
use InvalidArgumentException;
67

78
class Breadcrumb
@@ -129,7 +130,7 @@ public function __construct($name, $type, array $metaData = [])
129130
throw new InvalidArgumentException(sprintf('The breadcrumb type must be one of the set of %d standard types.', count($types)));
130131
}
131132

132-
$this->timestamp = gmdate('Y-m-d\TH:i:s\Z');
133+
$this->timestamp = Date::now();
133134
$this->name = $name;
134135
$this->type = $type;
135136
$this->metaData = $metaData;

src/Configuration.php

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class Configuration
8282
*/
8383
protected $notifier = [
8484
'name' => 'Bugsnag PHP (Official)',
85-
'version' => '3.23.1',
85+
'version' => '3.24.0',
8686
'url' => 'https://bugsnag.com',
8787
];
8888

@@ -569,11 +569,68 @@ public function getMetaData()
569569
*/
570570
public function setErrorReportingLevel($errorReportingLevel)
571571
{
572+
if (!$this->isSubsetOfErrorReporting($errorReportingLevel)) {
573+
$missingLevels = implode(', ', $this->getMissingErrorLevelNames($errorReportingLevel));
574+
$message =
575+
'Bugsnag Warning: errorReportingLevel cannot contain values that are not in error_reporting. '.
576+
"Any errors of these levels will be ignored: {$missingLevels}.";
577+
578+
error_log($message);
579+
}
580+
572581
$this->errorReportingLevel = $errorReportingLevel;
573582

574583
return $this;
575584
}
576585

586+
/**
587+
* Check if the given error reporting level is a subset of error_reporting.
588+
*
589+
* For example, if $level contains E_WARNING then error_reporting must too.
590+
*
591+
* @param int|null $level
592+
*
593+
* @return bool
594+
*/
595+
private function isSubsetOfErrorReporting($level)
596+
{
597+
if (!is_int($level)) {
598+
return true;
599+
}
600+
601+
$errorReporting = error_reporting();
602+
603+
// If all of the bits in $level are also in $errorReporting, ORing them
604+
// together will result in the same value as $errorReporting because
605+
// there are no new bits to add
606+
return ($errorReporting | $level) === $errorReporting;
607+
}
608+
609+
/**
610+
* Get a list of error level names that are in $level but not error_reporting.
611+
*
612+
* For example, if error_reporting is E_NOTICE and $level is E_ERROR then
613+
* this will return ['E_ERROR']
614+
*
615+
* @param int $level
616+
*
617+
* @return string[]
618+
*/
619+
private function getMissingErrorLevelNames($level)
620+
{
621+
$missingLevels = [];
622+
$errorReporting = error_reporting();
623+
624+
foreach (ErrorTypes::getAllCodes() as $code) {
625+
// $code is "missing" if it's in $level but not in $errorReporting
626+
if (($code & $level) && !($code & $errorReporting)) {
627+
$missingLevels[] = ErrorTypes::codeToString($code);
628+
}
629+
}
630+
631+
return $missingLevels;
632+
}
633+
577634
/**
578635
* Should we ignore the given error code?
579636
*
@@ -583,19 +640,19 @@ public function setErrorReportingLevel($errorReportingLevel)
583640
*/
584641
public function shouldIgnoreErrorCode($code)
585642
{
586-
$defaultReportingLevel = error_reporting();
587-
588-
if ($defaultReportingLevel === 0) {
589-
// The error has been suppressed using the error control operator ('@')
590-
// Ignore the error in all cases.
643+
// If the code is not in error_reporting then it is either totally
644+
// disabled or is being suppressed with '@'
645+
if (!(error_reporting() & $code)) {
591646
return true;
592647
}
593648

649+
// Filter the error code further against our error reporting level, which
650+
// can be lower than error_reporting
594651
if (isset($this->errorReportingLevel)) {
595652
return !($this->errorReportingLevel & $code);
596653
}
597654

598-
return !($defaultReportingLevel & $code);
655+
return false;
599656
}
600657

601658
/**

src/DateTime/Clock.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Bugsnag\DateTime;
4+
5+
use DateTimeImmutable;
6+
7+
final class Clock implements ClockInterface
8+
{
9+
/**
10+
* @return DateTimeImmutable
11+
*/
12+
public function now()
13+
{
14+
return new DateTimeImmutable();
15+
}
16+
}

src/DateTime/ClockInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Bugsnag\DateTime;
4+
5+
use DateTimeImmutable;
6+
7+
interface ClockInterface
8+
{
9+
/**
10+
* @return DateTimeImmutable
11+
*/
12+
public function now();
13+
}

src/DateTime/Date.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Bugsnag\DateTime;
4+
5+
use DateTimeImmutable;
6+
7+
final class Date
8+
{
9+
/**
10+
* @return string
11+
*/
12+
public static function now(ClockInterface $clock = null)
13+
{
14+
if ($clock === null) {
15+
$clock = new Clock();
16+
}
17+
18+
$date = $clock->now();
19+
20+
return self::format($date);
21+
}
22+
23+
/**
24+
* @param DateTimeImmutable $date
25+
*
26+
* @return string
27+
*/
28+
private static function format(DateTimeImmutable $date)
29+
{
30+
$dateTime = $date->format('Y-m-d\TH:i:s');
31+
32+
// The milliseconds format character ("v") was introduced in PHP 7.0, so
33+
// we need to take microseconds (PHP 5.2+) and convert to milliseconds
34+
$microseconds = $date->format('u');
35+
$milliseconds = substr($microseconds, 0, 3);
36+
37+
$offset = $date->format('P');
38+
39+
return "{$dateTime}.{$milliseconds}{$offset}";
40+
}
41+
}

src/ErrorTypes.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,76 @@ public static function getLevelsForSeverity($severity)
149149

150150
return $levels;
151151
}
152+
153+
/**
154+
* Get a list of all PHP error codes.
155+
*
156+
* @return int[]
157+
*/
158+
public static function getAllCodes()
159+
{
160+
return array_keys(self::$ERROR_TYPES);
161+
}
162+
163+
/**
164+
* Convert the given error code to a string representation.
165+
*
166+
* For example, E_ERROR => 'E_ERROR'.
167+
*
168+
* @param int $code
169+
*
170+
* @return string
171+
*/
172+
public static function codeToString($code)
173+
{
174+
switch ($code) {
175+
case E_ERROR:
176+
return 'E_ERROR';
177+
178+
case E_WARNING:
179+
return 'E_WARNING';
180+
181+
case E_PARSE:
182+
return 'E_PARSE';
183+
184+
case E_NOTICE:
185+
return 'E_NOTICE';
186+
187+
case E_CORE_ERROR:
188+
return 'E_CORE_ERROR';
189+
190+
case E_CORE_WARNING:
191+
return 'E_CORE_WARNING';
192+
193+
case E_COMPILE_ERROR:
194+
return 'E_COMPILE_ERROR';
195+
196+
case E_COMPILE_WARNING:
197+
return 'E_COMPILE_WARNING';
198+
199+
case E_USER_ERROR:
200+
return 'E_USER_ERROR';
201+
202+
case E_USER_WARNING:
203+
return 'E_USER_WARNING';
204+
205+
case E_USER_NOTICE:
206+
return 'E_USER_NOTICE';
207+
208+
case E_STRICT:
209+
return 'E_STRICT';
210+
211+
case E_RECOVERABLE_ERROR:
212+
return 'E_RECOVERABLE_ERROR';
213+
214+
case E_DEPRECATED:
215+
return 'E_DEPRECATED';
216+
217+
case E_USER_DEPRECATED:
218+
return 'E_USER_DEPRECATED';
219+
220+
default:
221+
return 'Unknown';
222+
}
223+
}
152224
}

src/HttpClient.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Bugsnag;
44

5+
use Bugsnag\DateTime\Date;
56
use Exception;
67
use GuzzleHttp\ClientInterface;
78
use RuntimeException;
@@ -251,7 +252,7 @@ protected function getHeaders($version = self::NOTIFY_PAYLOAD_VERSION)
251252
{
252253
return [
253254
'Bugsnag-Api-Key' => $this->config->getApiKey(),
254-
'Bugsnag-Sent-At' => strftime('%Y-%m-%dT%H:%M:%S'),
255+
'Bugsnag-Sent-At' => Date::now(),
255256
'Bugsnag-Payload-Version' => $version,
256257
];
257258
}

src/Report.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Bugsnag;
44

55
use Bugsnag\Breadcrumbs\Breadcrumb;
6+
use Bugsnag\DateTime\Date;
67
use Exception;
78
use InvalidArgumentException;
89
use Throwable;
@@ -204,7 +205,7 @@ public static function fromNamedError(Configuration $config, $name, $message = n
204205
protected function __construct(Configuration $config)
205206
{
206207
$this->config = $config;
207-
$this->time = gmdate('Y-m-d\TH:i:s\Z');
208+
$this->time = Date::now();
208209
}
209210

210211
/**

0 commit comments

Comments
 (0)