Skip to content

Commit

Permalink
Release v3.24.0
Browse files Browse the repository at this point in the history
  • Loading branch information
imjoehaines committed Oct 27, 2020
2 parents 138d6c3 + 355c7ef commit 8d470f4
Show file tree
Hide file tree
Showing 22 changed files with 672 additions and 46 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Changelog
=========

## 3.24.0 (2020-10-27)

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`

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

### Enhancements

* Improve the display of breadrumbs in the Bugsnag app by including milliseconds in timestamps
[#612](https://github.com/bugsnag/bugsnag-php/pull/612)

### Fixes

* Make `Configuration::shouldIgnoreErrorCode` compatible with PHP 8 by requiring the `errorReportingLevel` option to be a subset of `error_reporting`
[#611](https://github.com/bugsnag/bugsnag-php/pull/611)

## 3.23.1 (2020-10-19)

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.
Expand Down
5 changes: 5 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@
<directory suffix=".phpt">./tests/phpt</directory>
</testsuite>
</testsuites>

<php>
<!-- Use a very big number as we can't use the 'E_ALL' constant here -->
<ini name="error_reporting" value="2147483647" />
</php>
</phpunit>
3 changes: 2 additions & 1 deletion src/Breadcrumbs/Breadcrumb.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bugsnag\Breadcrumbs;

use Bugsnag\DateTime\Date;
use InvalidArgumentException;

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

$this->timestamp = gmdate('Y-m-d\TH:i:s\Z');
$this->timestamp = Date::now();
$this->name = $name;
$this->type = $type;
$this->metaData = $metaData;
Expand Down
71 changes: 64 additions & 7 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Configuration
*/
protected $notifier = [
'name' => 'Bugsnag PHP (Official)',
'version' => '3.23.1',
'version' => '3.24.0',
'url' => 'https://bugsnag.com',
];

Expand Down Expand Up @@ -569,11 +569,68 @@ public function getMetaData()
*/
public function setErrorReportingLevel($errorReportingLevel)
{
if (!$this->isSubsetOfErrorReporting($errorReportingLevel)) {
$missingLevels = implode(', ', $this->getMissingErrorLevelNames($errorReportingLevel));
$message =
'Bugsnag Warning: errorReportingLevel cannot contain values that are not in error_reporting. '.
"Any errors of these levels will be ignored: {$missingLevels}.";

error_log($message);
}

$this->errorReportingLevel = $errorReportingLevel;

return $this;
}

/**
* Check if the given error reporting level is a subset of error_reporting.
*
* For example, if $level contains E_WARNING then error_reporting must too.
*
* @param int|null $level
*
* @return bool
*/
private function isSubsetOfErrorReporting($level)
{
if (!is_int($level)) {
return true;
}

$errorReporting = error_reporting();

// If all of the bits in $level are also in $errorReporting, ORing them
// together will result in the same value as $errorReporting because
// there are no new bits to add
return ($errorReporting | $level) === $errorReporting;
}

/**
* Get a list of error level names that are in $level but not error_reporting.
*
* For example, if error_reporting is E_NOTICE and $level is E_ERROR then
* this will return ['E_ERROR']
*
* @param int $level
*
* @return string[]
*/
private function getMissingErrorLevelNames($level)
{
$missingLevels = [];
$errorReporting = error_reporting();

foreach (ErrorTypes::getAllCodes() as $code) {
// $code is "missing" if it's in $level but not in $errorReporting
if (($code & $level) && !($code & $errorReporting)) {
$missingLevels[] = ErrorTypes::codeToString($code);
}
}

return $missingLevels;
}

/**
* Should we ignore the given error code?
*
Expand All @@ -583,19 +640,19 @@ public function setErrorReportingLevel($errorReportingLevel)
*/
public function shouldIgnoreErrorCode($code)
{
$defaultReportingLevel = error_reporting();

if ($defaultReportingLevel === 0) {
// The error has been suppressed using the error control operator ('@')
// Ignore the error in all cases.
// If the code is not in error_reporting then it is either totally
// disabled or is being suppressed with '@'
if (!(error_reporting() & $code)) {
return true;
}

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

return !($defaultReportingLevel & $code);
return false;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/DateTime/Clock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Bugsnag\DateTime;

use DateTimeImmutable;

final class Clock implements ClockInterface
{
/**
* @return DateTimeImmutable
*/
public function now()
{
return new DateTimeImmutable();
}
}
13 changes: 13 additions & 0 deletions src/DateTime/ClockInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Bugsnag\DateTime;

use DateTimeImmutable;

interface ClockInterface
{
/**
* @return DateTimeImmutable
*/
public function now();
}
41 changes: 41 additions & 0 deletions src/DateTime/Date.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Bugsnag\DateTime;

use DateTimeImmutable;

final class Date
{
/**
* @return string
*/
public static function now(ClockInterface $clock = null)
{
if ($clock === null) {
$clock = new Clock();
}

$date = $clock->now();

return self::format($date);
}

/**
* @param DateTimeImmutable $date
*
* @return string
*/
private static function format(DateTimeImmutable $date)
{
$dateTime = $date->format('Y-m-d\TH:i:s');

// The milliseconds format character ("v") was introduced in PHP 7.0, so
// we need to take microseconds (PHP 5.2+) and convert to milliseconds
$microseconds = $date->format('u');
$milliseconds = substr($microseconds, 0, 3);

$offset = $date->format('P');

return "{$dateTime}.{$milliseconds}{$offset}";
}
}
72 changes: 72 additions & 0 deletions src/ErrorTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,76 @@ public static function getLevelsForSeverity($severity)

return $levels;
}

/**
* Get a list of all PHP error codes.
*
* @return int[]
*/
public static function getAllCodes()
{
return array_keys(self::$ERROR_TYPES);
}

/**
* Convert the given error code to a string representation.
*
* For example, E_ERROR => 'E_ERROR'.
*
* @param int $code
*
* @return string
*/
public static function codeToString($code)
{
switch ($code) {
case E_ERROR:
return 'E_ERROR';

case E_WARNING:
return 'E_WARNING';

case E_PARSE:
return 'E_PARSE';

case E_NOTICE:
return 'E_NOTICE';

case E_CORE_ERROR:
return 'E_CORE_ERROR';

case E_CORE_WARNING:
return 'E_CORE_WARNING';

case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';

case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';

case E_USER_ERROR:
return 'E_USER_ERROR';

case E_USER_WARNING:
return 'E_USER_WARNING';

case E_USER_NOTICE:
return 'E_USER_NOTICE';

case E_STRICT:
return 'E_STRICT';

case E_RECOVERABLE_ERROR:
return 'E_RECOVERABLE_ERROR';

case E_DEPRECATED:
return 'E_DEPRECATED';

case E_USER_DEPRECATED:
return 'E_USER_DEPRECATED';

default:
return 'Unknown';
}
}
}
3 changes: 2 additions & 1 deletion src/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bugsnag;

use Bugsnag\DateTime\Date;
use Exception;
use GuzzleHttp\ClientInterface;
use RuntimeException;
Expand Down Expand Up @@ -251,7 +252,7 @@ protected function getHeaders($version = self::NOTIFY_PAYLOAD_VERSION)
{
return [
'Bugsnag-Api-Key' => $this->config->getApiKey(),
'Bugsnag-Sent-At' => strftime('%Y-%m-%dT%H:%M:%S'),
'Bugsnag-Sent-At' => Date::now(),
'Bugsnag-Payload-Version' => $version,
];
}
Expand Down
3 changes: 2 additions & 1 deletion src/Report.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bugsnag;

use Bugsnag\Breadcrumbs\Breadcrumb;
use Bugsnag\DateTime\Date;
use Exception;
use InvalidArgumentException;
use Throwable;
Expand Down Expand Up @@ -204,7 +205,7 @@ public static function fromNamedError(Configuration $config, $name, $message = n
protected function __construct(Configuration $config)
{
$this->config = $config;
$this->time = gmdate('Y-m-d\TH:i:s\Z');
$this->time = Date::now();
}

/**
Expand Down
44 changes: 44 additions & 0 deletions tests/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bugsnag\Tests;

use DateTimeImmutable;
use InvalidArgumentException;
use PHPUnit\Framework\Assert as PhpUnitAssert;

Expand Down Expand Up @@ -72,4 +73,47 @@ public static function isType($type, $value)

$typeToAssertion[$type]($value);
}

/**
* @param string $format
* @param string $dateString
*
* @return void
*/
public static function matchesDateFormat($format, $dateString)
{
$date = new DateTimeImmutable($dateString);

PhpUnitAssert::assertSame(
$dateString,
$date->format($format),
"Date '{$dateString}' did not match format '{$format}'"
);
}

/**
* @param string $date
*
* @return void
*/
public static function matchesBugsnagDateFormat($date)
{
// The millisecond format specifier ("v") was added in PHP 7.0
if (PHP_MAJOR_VERSION >= 7) {
Assert::matchesDateFormat('Y-m-d\TH:i:s.vP', $date);

return;
}

$regex = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\.\d{3}([+-]\d{2}:\d{2})$/';

Assert::matchesRegularExpression($regex, $date);

preg_match($regex, $date, $matches);

$dateTime = $matches[1];
$offset = $matches[2];

Assert::matchesDateFormat('Y-m-d\TH:i:sP', $dateTime.$offset);
}
}
Loading

0 comments on commit 8d470f4

Please sign in to comment.